Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
152 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ package: | |
upload: | ||
twine upload dist/* | ||
|
||
test: lint | ||
test: | ||
pytest ./tests | ||
|
||
tox: | ||
|
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,104 @@ | ||
from collections import namedtuple | ||
from functools import wraps | ||
|
||
from pyee import EventEmitter | ||
|
||
EventHandler = namedtuple("Event", field_names=["event", "method"]) | ||
|
||
|
||
class Handlers: | ||
def __init__(self): | ||
self.reset() | ||
|
||
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): | ||
""" | ||
Register an event handler on an evented class. See the ``evented`` class | ||
decorator for a full example. | ||
""" | ||
|
||
def decorator(method): | ||
_handlers.append(EventHandler(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 | ||
|
||
|
||
def evented(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(_handlers) | ||
_handlers.reset() | ||
|
||
og_init = 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() |