Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add class decorator API * Add autofunctions for pyee.cls * Remove travis file * docs: Fix a few typos (#91) * Type Annotations (#97) * Set up virtualenv, pyright and isort * Run isort * Passing type annotations for base.py * action to run type checks * Alas! * Happy type checker for trio * MOST of the library is type-checking * working, non-cranky type annotations for uplift laul * Type check the tests, cause an explosion * Clean up requirements.txt * tests type-checking * py.typed file * tests and linting happy * Update build * obvious action bugfix * trailing comma * remove inconsequential and angry type annotation * Ignore type issues w asyncio import * messy typecast * anyway thats when I started blasting * carnage! * uplift bugfixes * update pytest * bye 3.6 * type annotations for cls Co-authored-by: Tim Gates <tim.gates@iress.com>
- Loading branch information
1 parent
049dbdc
commit 2e212b0
Showing
3 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
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(name="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:: | ||
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``:: | ||
@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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# -*- coding: utf-8 -*- | ||
from mock import Mock | ||
import pytest | ||
|
||
from pyee import EventEmitter | ||
from pyee.cls import evented, on | ||
|
||
|
||
@evented | ||
class EventedFixture: | ||
def __init__(self): | ||
self.call_me = Mock() | ||
|
||
@on("event") | ||
def event_handler(self, *args, **kwargs): | ||
self.call_me(self, *args, **kwargs) | ||
|
||
|
||
_custom_event_emitter = EventEmitter() | ||
|
||
|
||
@evented | ||
class CustomEmitterFixture: | ||
def __init__(self): | ||
self.call_me = Mock() | ||
self.event_emitter = _custom_event_emitter | ||
|
||
@on("event") | ||
def event_handler(self, *args, **kwargs): | ||
self.call_me(self, *args, **kwargs) | ||
|
||
|
||
class InheritedFixture(EventedFixture): | ||
pass | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"cls", [EventedFixture, CustomEmitterFixture, InheritedFixture] | ||
) | ||
def test_evented_decorator(cls): | ||
inst = cls() | ||
|
||
inst.event_emitter.emit("event", "emitter is emitted!") | ||
|
||
inst.call_me.assert_called_once_with(inst, "emitter is emitted!") | ||
|
||
_custom_event_emitter.remove_all_listeners() |