116 lines
2.8 KiB
Python
116 lines
2.8 KiB
Python
from dataclasses import dataclass
|
|
from functools import wraps
|
|
from typing import Callable, List, Type, TypeVar
|
|
|
|
from pyee import EventEmitter
|
|
|
|
|
|
@dataclass
|
|
class Handler:
|
|
event: str
|
|
method: Callable
|
|
|
|
|
|
class Handlers:
|
|
def __init__(self):
|
|
self._handlers: List[Handler] = []
|
|
|
|
def append(self, handler):
|
|
self._handlers.append(handler)
|
|
|
|
def __iter__(self):
|
|
return iter(self._handlers)
|
|
|
|
def reset(self):
|
|
self._handlers = []
|
|
|
|
|
|
_handlers = Handlers()
|
|
|
|
|
|
def on(event: str) -> Callable[[Callable], Callable]:
|
|
"""
|
|
Register an event handler on an evented class. See the `evented` class
|
|
decorator for a full example.
|
|
"""
|
|
|
|
def decorator(method: Callable) -> Callable:
|
|
_handlers.append(Handler(event=event, method=method))
|
|
return method
|
|
|
|
return decorator
|
|
|
|
|
|
def _bind(self, method):
|
|
@wraps(method)
|
|
def bound(*args, **kwargs):
|
|
return method(self, *args, **kwargs)
|
|
|
|
return bound
|
|
|
|
|
|
Cls = TypeVar("Cls", bound=Type)
|
|
|
|
|
|
def evented(cls: Cls) -> Cls:
|
|
"""
|
|
Configure an evented class.
|
|
|
|
Evented classes are classes which use an EventEmitter to call instance
|
|
methods during runtime. To achieve this without this helper, you would
|
|
instantiate an `EventEmitter` in the `__init__` method and then call
|
|
`event_emitter.on` for every method on `self`.
|
|
|
|
This decorator and the `on` function help make things look a little nicer
|
|
by defining the event handler on the method in the class and then adding
|
|
the `__init__` hook in a wrapper:
|
|
|
|
```py
|
|
from pyee.cls import evented, on
|
|
|
|
@evented
|
|
class Evented:
|
|
@on("event")
|
|
def event_handler(self, *args, **kwargs):
|
|
print(self, args, kwargs)
|
|
|
|
evented_obj = Evented()
|
|
|
|
evented_obj.event_emitter.emit(
|
|
"event", "hello world", numbers=[1, 2, 3]
|
|
)
|
|
```
|
|
|
|
The `__init__` wrapper will create a `self.event_emitter: EventEmitter`
|
|
automatically but you can also define your own event_emitter inside your
|
|
class's unwrapped `__init__` method. For example, to use this
|
|
decorator with a `TwistedEventEmitter`::
|
|
|
|
```py
|
|
@evented
|
|
class Evented:
|
|
def __init__(self):
|
|
self.event_emitter = TwistedEventEmitter()
|
|
|
|
@on("event")
|
|
async def event_handler(self, *args, **kwargs):
|
|
await self.some_async_action(*args, **kwargs)
|
|
```
|
|
"""
|
|
handlers: List[Handler] = list(_handlers)
|
|
_handlers.reset()
|
|
|
|
og_init: Callable = cls.__init__
|
|
|
|
@wraps(cls.__init__)
|
|
def init(self, *args, **kwargs):
|
|
og_init(self, *args, **kwargs)
|
|
if not hasattr(self, "event_emitter"):
|
|
self.event_emitter = EventEmitter()
|
|
|
|
for h in handlers:
|
|
self.event_emitter.on(h.event, _bind(self, h.method))
|
|
|
|
cls.__init__ = init
|
|
|
|
return cls
|