pyee-3.0.3/0000755000175000017500000000000013111375313011435 5ustar ethanethanpyee-3.0.3/version.txt0000644000175000017500000000002013111375250013653 0ustar ethanethan3.0.3-0-gb719423pyee-3.0.3/tests/0000755000175000017500000000000013050210651012572 5ustar ethanethanpyee-3.0.3/tests/test_sync.py0000644000175000017500000001314213047466534015203 0ustar ethanethan# -*- coding: utf-8 -*- from inspect import getmro from mock import Mock from pytest import raises from pyee import EventEmitter class PyeeTestException(Exception): pass def test_emit_sync(): """Basic synchronous emission works""" call_me = Mock() ee = EventEmitter() @ee.on('event') def event_handler(data, **kwargs): call_me() assert data == 'emitter is emitted!' # Making sure data is passed propers ee.emit('event', 'emitter is emitted!', error=False) call_me.assert_called_once() def test_emit_error(): """Errors raise with no event handler, otherwise emit on handler""" call_me = Mock() ee = EventEmitter() test_exception = PyeeTestException('lololol') with raises(PyeeTestException) as exc_info: ee.emit('error', test_exception) @ee.on('error') def on_error(exc): call_me() # No longer raises and error instead return True indicating handled assert ee.emit('error', test_exception) call_me.assert_called_once() def test_emit_return(): """Emit returns True when handlers are registered on an event, and false otherwise. """ call_me = Mock() ee = EventEmitter() # make sure emitting without a callback returns False assert not ee.emit('data') # add a callback ee.on('data')(call_me) # should return True now assert ee.emit('data') def test_new_listener_event(): """The 'new_listener' event fires whenever a new listerner is added.""" call_me = Mock() ee = EventEmitter() ee.on('new_listener', call_me) # Should fire new_listener event @ee.on('event') def event_handler(data): pass call_me.assert_called_once() def test_listener_removal(): """Removing listeners removes the correct listener from an event.""" ee = EventEmitter() # Some functions to pass to the EE def first(): return 1 ee.on('event', first) @ee.on('event') def second(): return 2 @ee.on('event') def third(): return 3 def fourth(): return 4 ee.on('event', fourth) assert ee._events['event'] == [first, second, third, fourth] ee.remove_listener('event', second) assert ee._events['event'] == [first, third, fourth] ee.remove_listener('event', first) assert ee._events['event'] == [third, fourth] ee.remove_all_listeners('event') assert ee._events['event'] == [] def test_listener_removal_on_emit(): """Test that a listener removed during an emit is called inside the current emit cycle. """ call_me = Mock() ee = EventEmitter() def should_remove(): ee.remove_listener('remove', call_me) ee.on('remove', should_remove) ee.on('remove', call_me) ee.emit('remove') call_me.assert_called_once() call_me.reset_mock() # Also test with the listeners added in the opposite order ee = EventEmitter() ee.on('remove', call_me) ee.on('remove', should_remove) ee.emit('remove') call_me.assert_called_once() def test_once(): """Test that `once()` method works propers. """ # very similar to "test_emit" but also makes sure that the event # gets removed afterwards call_me = Mock() ee = EventEmitter() def once_handler(data): assert data == 'emitter is emitted!' call_me() # Tests to make sure that after event is emitted that it's gone. callback_fn = ee.once('event', once_handler) # assert ee._events['event'] == [callback_fn] ee.emit('event', 'emitter is emitted!') call_me.assert_called_once() assert ee._events['event'] == [] def test_listeners(): """`listeners()` gives you access to the listeners array.""" call_me = Mock() ee = EventEmitter() @ee.on('event') def event_handler(): pass listeners = ee.listeners('event') assert listeners[0] == event_handler # Overwrite listener listeners[0] = call_me ee.emit('event') call_me.assert_called_once() def test_properties_preserved(): """Test that the properties of decorated functions are preserved.""" call_me = Mock() call_me_also = Mock() ee = EventEmitter() @ee.on('always') def always_event_handler(): """An event handler.""" call_me() @ee.once('once') def once_event_handler(): """Another event handler.""" call_me_also() assert always_event_handler.__doc__ == 'An event handler.' assert once_event_handler.__doc__ == 'Another event handler.' always_event_handler() call_me.assert_called_once() once_event_handler() call_me_also.assert_called_once() call_me_also.reset_mock() # Calling the event handler directly doesn't clear the handler ee.emit('once') call_me_also.assert_called_once() def test_inheritance(): """Test that inheritance is preserved from object""" assert object in getmro(EventEmitter) class example(EventEmitter): def __init__(self): super(example, self).__init__() assert EventEmitter in getmro(example) assert object in getmro(example) def test_multiple_inheritance(): """Test that inheritance is preserved along a lengthy MRO""" class example(EventEmitter): def __init__(self): super(example, self).__init__() class _example(example): def __init__(self): super(_example, self).__init__() class example2(_example): def __init__(self): super(example2, self).__init__() class _example2(_example): def __init__(self): super(_example2, self).__init__() a = _example2() pyee-3.0.3/tests/test_async.py0000644000175000017500000000510013047467712015336 0ustar ethanethan# -*- coding: utf-8 -*- from asyncio import Future, gather, new_event_loop, sleep from mock import Mock from twisted.internet.defer import ensureDeferred from pyee import EventEmitter class PyeeTestError(Exception): pass def test_asyncio_emit(): """Test that event_emitters can handle wrapping coroutines as used with asyncio. """ loop = new_event_loop() ee = EventEmitter(loop=loop) should_call = Future(loop=loop) @ee.on('event') async def event_handler(): should_call.set_result(True) async def create_timeout(loop=loop): await sleep(0.1, loop=loop) if not should_call.done(): raise Exception('should_call timed out!') return should_call.cancel() timeout = create_timeout(loop=loop) @should_call.add_done_callback def _done(result): assert result ee.emit('event') loop.run_until_complete(gather(should_call, timeout, loop=loop)) loop.close() def test_asyncio_error(): """Test that event_emitters can handle errors when wrapping coroutines as used with asyncio. """ loop = new_event_loop() ee = EventEmitter(loop=loop) should_call = Future(loop=loop) @ee.on('event') async def event_handler(): raise PyeeTestError() @ee.on('error') def handle_error(exc): should_call.set_result(exc) async def create_timeout(loop=loop): await sleep(0.1, loop=loop) if not should_call.done(): raise Exception('should_call timed out!') return should_call.cancel() timeout = create_timeout(loop=loop) @should_call.add_done_callback def _done(result): assert isinstance(result, PyeeTestError) ee.emit('event') loop.run_until_complete(gather(should_call, timeout, loop=loop)) loop.close() def test_twisted_emit(): """Test that event_emitters can handle wrapping coroutines when using twisted and ensureDeferred. """ ee = EventEmitter(scheduler=ensureDeferred) should_call = Mock() @ee.on('event') async def event_handler(): should_call(True) ee.emit('event') should_call.assert_called_once() def test_twisted_error(): """Test that event_emitters can handle wrapping coroutines when using twisted and ensureDeferred. """ ee = EventEmitter(scheduler=ensureDeferred) should_call = Mock() @ee.on('event') async def event_handler(): raise PyeeTestError() @ee.on('error') def handle_error(e): should_call(e) ee.emit('event') should_call.assert_called_once() pyee-3.0.3/tests/conftest.py0000644000175000017500000000023612774056703015014 0ustar ethanethan# -*- coding: utf-8 -*- from sys import version_info as v collect_ignore = [] if not (v[0] >= 3 and v[1] >= 5): collect_ignore.append('test_async.py') pyee-3.0.3/pyee.egg-info/0000755000175000017500000000000013050210651014064 5ustar ethanethanpyee-3.0.3/pyee.egg-info/dependency_links.txt0000644000175000017500000000000113050210651020132 0ustar ethanethan pyee-3.0.3/pyee.egg-info/SOURCES.txt0000644000175000017500000000035713050210651015755 0ustar ethanethanMANIFEST.in README.rst setup.cfg setup.py version.txt pyee/__init__.py pyee.egg-info/PKG-INFO pyee.egg-info/SOURCES.txt pyee.egg-info/dependency_links.txt pyee.egg-info/top_level.txt tests/conftest.py tests/test_async.py tests/test_sync.pypyee-3.0.3/pyee.egg-info/top_level.txt0000644000175000017500000000000513050210651016611 0ustar ethanethanpyee pyee-3.0.3/pyee.egg-info/PKG-INFO0000644000175000017500000000361613050210651015167 0ustar ethanethanMetadata-Version: 1.1 Name: pyee Version: 3.0.3 Summary: A port of node.js's EventEmitter to python. Home-page: https://github.com/jfhbrook/pyee Author: Joshua Holbrook Author-email: josh.holbrook@gmail.com License: UNKNOWN Description: pyee ==== .. image:: https://travis-ci.org/jfhbrook/pyee.png :target: https://travis-ci.org/jfhbrook/pyee .. image:: https://readthedocs.org/projects/pyee/badge/?version=latest :target: https://pyee.readthedocs.io pyee supplies an ``EventEmitter`` object similar to the ``EventEmitter`` from Node.js. Docs: ----- Authgenerated API docs, including basic installation directions and examples, can be found at https://pyee.readthedocs.io . Development: ------------ - For development purposes, you can link this project to your global space with ``python setup.py develop``. - Tests can be ran with ``python setup.py test``. - Documentation can be generated with ``cd ./docs && make html``. Changelog: ---------- https://github.com/jfhbrook/pyee/blob/master/CHANGELOG.md License: -------- MIT/X11, see LICENSE file. Keywords: events,emitter,node.js,node,eventemitter,event_emitter Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Other/Nonlisted Topic pyee-3.0.3/PKG-INFO0000644000175000017500000000361613050210651012533 0ustar ethanethanMetadata-Version: 1.1 Name: pyee Version: 3.0.3 Summary: A port of node.js's EventEmitter to python. Home-page: https://github.com/jfhbrook/pyee Author: Joshua Holbrook Author-email: josh.holbrook@gmail.com License: UNKNOWN Description: pyee ==== .. image:: https://travis-ci.org/jfhbrook/pyee.png :target: https://travis-ci.org/jfhbrook/pyee .. image:: https://readthedocs.org/projects/pyee/badge/?version=latest :target: https://pyee.readthedocs.io pyee supplies an ``EventEmitter`` object similar to the ``EventEmitter`` from Node.js. Docs: ----- Authgenerated API docs, including basic installation directions and examples, can be found at https://pyee.readthedocs.io . Development: ------------ - For development purposes, you can link this project to your global space with ``python setup.py develop``. - Tests can be ran with ``python setup.py test``. - Documentation can be generated with ``cd ./docs && make html``. Changelog: ---------- https://github.com/jfhbrook/pyee/blob/master/CHANGELOG.md License: -------- MIT/X11, see LICENSE file. Keywords: events,emitter,node.js,node,eventemitter,event_emitter Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Other/Nonlisted Topic pyee-3.0.3/README.rst0000644000175000017500000000147712774050765013154 0ustar ethanethanpyee ==== .. image:: https://travis-ci.org/jfhbrook/pyee.png :target: https://travis-ci.org/jfhbrook/pyee .. image:: https://readthedocs.org/projects/pyee/badge/?version=latest :target: https://pyee.readthedocs.io pyee supplies an ``EventEmitter`` object similar to the ``EventEmitter`` from Node.js. Docs: ----- Authgenerated API docs, including basic installation directions and examples, can be found at https://pyee.readthedocs.io . Development: ------------ - For development purposes, you can link this project to your global space with ``python setup.py develop``. - Tests can be ran with ``python setup.py test``. - Documentation can be generated with ``cd ./docs && make html``. Changelog: ---------- https://github.com/jfhbrook/pyee/blob/master/CHANGELOG.md License: -------- MIT/X11, see LICENSE file. pyee-3.0.3/setup.cfg0000644000175000017500000000007713050210651013255 0ustar ethanethan[aliases] test = pytest [egg_info] tag_build = tag_date = 0 pyee-3.0.3/MANIFEST.in0000644000175000017500000000012612774236134013205 0ustar ethanethaninclude README.rst include CHANGELOG include version.txt recursive-include tests *.py pyee-3.0.3/setup.py0000644000175000017500000000237512774236672013200 0ustar ethanethan# -*- coding: utf-8 -*- from os import path from setuptools import find_packages, setup README_rst = path.join(path.abspath(path.dirname(__file__)), 'README.rst') with open(README_rst, 'r') as f: long_description = f.read() setup( name="pyee", vcversioner={}, packages=find_packages(), setup_requires=['pytest-runner', 'vcversioner'], tests_require=['twisted'], include_package_data=True, description="A port of node.js's EventEmitter to python.", long_description=long_description, author="Joshua Holbrook", author_email="josh.holbrook@gmail.com", url="https://github.com/jfhbrook/pyee", keywords=[ "events", "emitter", "node.js", "node", "eventemitter", "event_emitter" ], classifiers=[ "Programming Language :: Python", "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Topic :: Other/Nonlisted Topic" ] ) pyee-3.0.3/pyee/0000755000175000017500000000000013050210651012372 5ustar ethanethanpyee-3.0.3/pyee/__init__.py0000644000175000017500000001476713047466534014545 0ustar ethanethan# -*- coding: utf-8 -*- """ pyee supplies an ``EventEmitter`` object similar to the ``EventEmitter`` from Node.js. It supports both synchronous callbacks and asyncio coroutines. Example ------- :: In [1]: from pyee import EventEmitter In [2]: ee = EventEmitter() In [3]: @ee.on('event') ...: def event_handler(): ...: print('BANG BANG') ...: In [4]: ee.emit('event') BANG BANG In [5]: """ try: from asyncio import iscoroutine, ensure_future except ImportError: iscoroutine = None ensure_future = None from collections import defaultdict __all__ = ['EventEmitter', 'PyeeException'] class PyeeException(Exception): """An exception internal to pyee.""" pass class EventEmitter(object): """The EventEmitter class. For interoperation with asyncio, one can specify the scheduler and the event loop. The scheduler defaults to ``asyncio.ensure_future``, and the loop defaults to ``None``. When used with the default scheduler, this will schedule the coroutine onto asyncio's default loop. This should also be compatible with recent versions of twisted by setting ``scheduler=twisted.internet.defer.ensureDeferred``. Most events are registered with EventEmitter via the ``on`` and ``once`` methods. However, pyee EventEmitters have two *special* events: - ``new_listener``: Fires whenever a new listener is created. Listeners for this event do not fire upon their own creation. - ``error``: When emitted raises an Exception by default, behavior can be overriden by attaching callback to the event. For example:: @ee.on('error') def onError(message): logging.err(message) ee.emit('error', Exception('something blew up')) For synchronous callbacks, exceptions are **not** handled for you--- you must catch your own exceptions inside synchronous ``on`` handlers. However, when wrapping **async** functions, errors will be intercepted and emitted under the ``error`` event. **This behavior for async functions is inconsistent with node.js**, which unlike this package has no facilities for handling returned Promises from handlers. """ def __init__(self, scheduler=ensure_future, loop=None): self._events = defaultdict(list) self._schedule = scheduler self._loop = loop def on(self, event, f=None): """Registers the function (or optionally an asyncio coroutine function) ``f`` to the event name ``event``. If ``f`` isn't provided, this method returns a function that takes ``f`` as a callback; in other words, you can use this method as a decorator, like so:: @ee.on('data') def data_handler(data): print(data) As mentioned, this method can also take an asyncio coroutine function:: @ee.on('data') async def data_handler(data) await do_async_thing(data) This will automatically schedule the coroutine using the configured scheduling function (defaults to ``asyncio.ensure_future``) and the configured event loop (defaults to ``asyncio.get_event_loop()``). """ def _on(f): # Fire 'new_listener' *before* adding the new listener! self.emit('new_listener', event, f) # Add the necessary function self._events[event].append(f) # Return original function so removal works return f if f is None: return _on else: return _on(f) def emit(self, event, *args, **kwargs): """Emit ``event``, passing ``*args`` and ``**kwargs`` to each attached function. Returns ``True`` if any functions are attached to ``event``; otherwise returns ``False``. Example:: ee.emit('data', '00101001') Assuming ``data`` is an attached function, this will call ``data('00101001')'``. For coroutine event handlers, calling emit is non-blocking. In other words, you do not have to await any results from emit, and the coroutine is scheduled in a fire-and-forget fashion. """ handled = False # Copy the events dict first. Avoids a bug if the events dict gets # changed in the middle of the following for loop. events_copy = list(self._events[event]) # Pass the args to each function in the events dict for f in events_copy: result = f(*args, **kwargs) if iscoroutine and iscoroutine(result): if self._loop: d = self._schedule(result, loop=self._loop) else: d = self._schedule(result) if hasattr(d, 'add_done_callback'): @d.add_done_callback def _callback(f): exc = f.exception() if exc: self.emit('error', exc) elif hasattr(d, 'addErrback'): @d.addErrback def _callback(exc): self.emit('error', exc) handled = True if not handled and event == 'error': if len(args): raise args[0] else: raise PyeeException("Uncaught, unspecified 'error' event.") return handled def once(self, event, f=None): """The same as ``ee.on``, except that the listener is automatically removed after being called. """ def _once(f): def g(*args, **kwargs): f(*args, **kwargs) self.remove_listener(event, g) return g def _wrapper(f): self.on(event, _once(f)) return f if f is None: return _wrapper else: _wrapper(f) def remove_listener(self, event, f): """Removes the function ``f`` from ``event``.""" self._events[event].remove(f) def remove_all_listeners(self, event=None): """Remove all listeners attached to ``event``. If ``event`` is ``None``, remove all listeners on all events. """ if event is not None: self._events[event] = [] else: self._events = None self._events = defaultdict(list) def listeners(self, event): """Returns the list of all listeners registered to the ``event``. """ return self._events[event]