nocarryr/python-dispatch

New function: Use events globally

vojj opened this issue · 5 comments

vojj commented

Hello all,

Thanks for this great library.
I use python-dispatch in my small project.

I want to propose an easier way to use the functionality.

My req.:

  • use the eventDispatcher globally
  • any class can subscribe and emit an event

My code is in my app.py (main)

_event = None
_event = EventDispatcher()
_event.register_event('on_quit')

new wrapper class

from pydispatch import Dispatcher

class Eventhandler(Dispatcher):
    def __init__(self, event=None):
        pass

    # Events are defined in classes and subclasses with the '_events_' attribute
    _events_ = ['on_test']


_event: Eventhandler = None


class EventDispatcher:

    def __init__(self, event: Eventhandler = None):
        global _event
        if _event is None:
            _event = Eventhandler()

    def emit(self, event, data="emitted event"):
        global _event
        _event.emit(event, data=data)

    def register_event(self, event):
        global _event
        _event.register_event(event)
        print('Event added')

    def register(self, name, callback):
        global _event
        kwargs = {name: callback}
        _event.bind(**kwargs)

Any class can emit the event by name.

EventDispatcher().emit("on_quit", "PleaseDoSomethink")

It is not ready to use, but I can add the changes finally in a new brunch.

What do you think?

Best Regards
Daniel

An interesting concept, and I can see there being use cases for sure.

What if the front-facing API for this used module-level functions instead of exposing the global Dispatcher instance itself? Something like:

import pydispatch  # just importing the module itself, no classes, etc

pydispatch.register_event('on_quit')

def my_shutdown_callback(*args, **kwargs):
    pass

pydispatcher.bind(on_quit=my_shutdown_callback)

pydispatcher.emit('on_quit', 'foo', bar='bar')

pydispatcher.unbind(my_shutdown_callback)

Making things work like above would be fairly trivial:

# Somewhere in pydispatch, maybe __init__.py after the imports

_global_dispatcher = Dispatcher()

def register_event(name: str):
    _global_dispatcher.register_event(name)

def bind(**kwargs):
    _global_dispatcher.bind(**kwargs)

# etc
vojj commented

Hi,

I like your ideas. I guess the second one is a good way. But the changes are not so much. :-)
init.py could be an excellent way to generalize this uses case.

I've tested the code with a base-view to inherit in my views.

class Baseview:
    def __init__(self):
        # events
        self.events = EventDispatcher() 
class motor_control(Baseview):
    def __init__(self, targetFrame, title, motor, event=None):
        super().__init__(self)

It is working very well.

The critical case in this idea is to catch the same instance.

All best.

Now that I'm thinking about this with a fresh set of eyes, I think this would be generally something quite useful.

The approach that I'm thinking is a little along the lines of how Django signals work (only less restrictive).

The decorator shortcut for binding an event to a callback could also be implemented.

I'd like to ponder this and toy with it.

General Notes:

  • A global Dispatcher instance should be made available at the package level.
  • All necessary Dispatcher methods of the global instance should be added as functions at the package level mapping to the instance.
  • Thread safety needs to be addressed and thought through.
  • To avoid conflict with a single instance holding all events:
    • should event names may need to be name-spaced?
    • should an exception be raised in register_event if an event already exists with that name?
  • Binding to an event that hasn't been registered yet would be nice. Would require caching the callback and event name (shouldn't be too difficult).
  • Event emission can be done using the global Dispatcher.emit(<name>, ...) method, but exposing the Event instances themselves should be available:
import pydispatch

# Dispatcher.emit() method
pydispatch.register_event('signal_a')
pydispatch.emit('signal_a')

# Event object method
signal_b = pydispatch.register_event('signal_b')
signal_b.emit()

Closing this again and putting into a new issue (#18)

Thanks for the idea @vojj !