Hello community, here is the log from the commit of package python-pyee for openSUSE:Factory checked in at 2019-04-26 22:54:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyee (Old) and /work/SRC/openSUSE:Factory/.python-pyee.new.5536 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-pyee" Fri Apr 26 22:54:29 2019 rev:5 rq:697804 version:6.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyee/python-pyee.changes 2018-12-24 11:41:51.709427130 +0100 +++ /work/SRC/openSUSE:Factory/.python-pyee.new.5536/python-pyee.changes 2019-04-26 22:54:30.813312683 +0200 @@ -1,0 +2,13 @@ +Thu Apr 25 07:50:21 UTC 2019 - pgajdos@suse.com + +- version update to 6.0.0 + * Added a ``BaseEventEmitter`` class which is entirely synchronous and + intended for simple use and for subclassing + * Added an ``AsyncIOEventEmitter`` class for intended use with asyncio + * Added a ``TwistedEventEmitter`` class for intended use with twisted + * Added an ``ExecutorEventEmitter`` class which runs events in an executor + * Deprecated ``EventEmitter`` (use one of the new classes) +- modified patches + % fix-build-requirements.patch (refreshed) + +------------------------------------------------------------------- Old: ---- pyee-5.0.0.tar.gz New: ---- pyee-6.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyee.spec ++++++ --- /var/tmp/diff_new_pack.NNMqdu/_old 2019-04-26 22:54:31.573312193 +0200 +++ /var/tmp/diff_new_pack.NNMqdu/_new 2019-04-26 22:54:31.573312193 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-pyee # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without test Name: python-pyee -Version: 5.0.0 +Version: 6.0.0 Release: 0 Summary: A port of node.js's EventEmitter to python License: MIT @@ -52,6 +52,7 @@ %prep %setup -q -n pyee-%{version} %patch0 -p1 +# https://github.com/jfhbrook/pyee/issues/58 cp %{SOURCE99} . %build ++++++ fix-build-requirements.patch ++++++ --- /var/tmp/diff_new_pack.NNMqdu/_old 2019-04-26 22:54:31.601312176 +0200 +++ /var/tmp/diff_new_pack.NNMqdu/_new 2019-04-26 22:54:31.609312171 +0200 @@ -5,24 +5,24 @@ it was only used for tests. Fix it so we can build for all python flavors and run tests only for python3 (where pytest-asyncio is available). -Index: pyee-5.0.0/setup.py +Index: pyee-6.0.0/setup.py =================================================================== ---- pyee-5.0.0.orig/setup.py -+++ pyee-5.0.0/setup.py -@@ -14,11 +14,13 @@ setup( +--- pyee-6.0.0.orig/setup.py 2019-04-13 19:47:29.000000000 +0200 ++++ pyee-6.0.0/setup.py 2019-04-25 09:47:13.860816108 +0200 +@@ -14,11 +14,14 @@ setup( packages=find_packages(), setup_requires=[ -+ 'vcversioner', ++ 'vcversioner' + ], + tests_require=[ + 'twisted', ++ 'futures; python_version < "3.0"', 'pytest-runner', 'pytest-asyncio; python_version > "3.4"', - 'vcversioner' -- ], -- tests_require=['twisted'], -+ ], + ], +- tests_require=['twisted', 'futures; python_version < "3.0"'], include_package_data=True, description="A port of node.js's EventEmitter to python.", ++++++ pyee-5.0.0.tar.gz -> pyee-6.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/CHANGELOG.rst new/pyee-6.0.0/CHANGELOG.rst --- old/pyee-5.0.0/CHANGELOG.rst 2017-11-18 23:44:14.000000000 +0100 +++ new/pyee-6.0.0/CHANGELOG.rst 2019-04-13 19:47:29.000000000 +0200 @@ -1,3 +1,13 @@ +2019/04/11 Version 6.0.0 +----------------------- +- Added a ``BaseEventEmitter`` class which is entirely synchronous and + intended for simple use and for subclassing +- Added an ``AsyncIOEventEmitter`` class for intended use with asyncio +- Added a ``TwistedEventEmitter`` class for intended use with twisted +- Added an ``ExecutorEventEmitter`` class which runs events in an executor +- Deprecated ``EventEmitter`` (use one of the new classes) + + 2017/11/18 Version 5.0.0 ------------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/DEVELOPMENT.rst new/pyee-6.0.0/DEVELOPMENT.rst --- old/pyee-5.0.0/DEVELOPMENT.rst 2017-11-18 23:19:26.000000000 +0100 +++ new/pyee-6.0.0/DEVELOPMENT.rst 2019-04-13 19:50:11.000000000 +0200 @@ -1,13 +1,17 @@ Development And Publishing: ------------ -- Given you're in a virtualenv or conda environment, you can install this - project's dependencies with ``make setup``. -- You can link this project to your global space with +- Set up either a virtualenv or a conda env + - if using a virtualenv, `pip install -r requirements_dev.txt` will install + development dependencies + - if using conda, `conda env create` in this directory will create the + environment and `conda env update` will update. +- You can link this project to your current environment with ``python setup.py develop``. - Tests can be ran with ``make test``. -- Documentation can be generated locally with ``make the_docs``. -- Version off by ``git tag -a {version} -m 'Version {version}'``. No prefixed +- Documentation can be generated locally with ``make build_docs`` and served + with ``make serve_docs``. +- Version off by ``git tag -a {version} -m 'Release {version}'``. No prefixed v. Make sure you do a commit with the updated CHANGELOG as well. - Publish with ``make package`` followed by ``make upload``. - RTD should build automatically but can be kicked off manually by logging in. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/PKG-INFO new/pyee-6.0.0/PKG-INFO --- old/pyee-5.0.0/PKG-INFO 2017-11-18 23:44:51.000000000 +0100 +++ new/pyee-6.0.0/PKG-INFO 2019-04-13 19:50:13.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyee -Version: 5.0.0 +Version: 6.0.0 Summary: A port of node.js's EventEmitter to python. Home-page: https://github.com/jfhbrook/pyee Author: Joshua Holbrook @@ -14,8 +14,10 @@ .. 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. + pyee supplies a ``BaseEventEmitter`` object that is similar to the + ``EventEmitter`` class from Node.js. It also supplies a number of subclasses + with added support for async and threaded programming in python, such as + async/await as seen in python 3.5+. Docs: ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/README.rst new/pyee-6.0.0/README.rst --- old/pyee-5.0.0/README.rst 2017-11-18 23:22:07.000000000 +0100 +++ new/pyee-6.0.0/README.rst 2019-04-13 19:47:29.000000000 +0200 @@ -6,8 +6,10 @@ .. 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. +pyee supplies a ``BaseEventEmitter`` object that is similar to the +``EventEmitter`` class from Node.js. It also supplies a number of subclasses +with added support for async and threaded programming in python, such as +async/await as seen in python 3.5+. Docs: ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee/__init__.py new/pyee-6.0.0/pyee/__init__.py --- old/pyee-5.0.0/pyee/__init__.py 2017-11-18 23:19:26.000000000 +0100 +++ new/pyee-6.0.0/pyee/__init__.py 2019-04-13 19:47:29.000000000 +0200 @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- """ -pyee supplies an ``EventEmitter`` object similar to the ``EventEmitter`` -from Node.js. It supports both synchronous callbacks and asyncio coroutines. +pyee supplies a ``BaseEventEmitter`` class that is similar to the +``EventEmitter`` class from Node.js. In addition, it supplies the subclasses +``AsyncIOEventEmitter``, ``TwistedEventEmitter`` and ``ExecutorEventEmitter`` +for supporting async and threaded execution with asyncio, twisted, and +concurrent.futures Executors respectively, as supported by the environment. Example @@ -10,9 +13,9 @@ :: - In [1]: from pyee import EventEmitter + In [1]: from pyee import BaseEventEmitter - In [2]: ee = EventEmitter() + In [2]: ee = BaseEventEmitter() In [3]: @ee.on('event') ...: def event_handler(): @@ -26,194 +29,29 @@ """ -try: - from asyncio import iscoroutine, ensure_future -except ImportError: - iscoroutine = None - ensure_future = None - -from collections import defaultdict, OrderedDict +from pyee._base import ( + BaseEventEmitter, + PyeeException +) -__all__ = ['EventEmitter', 'PyeeException'] +from pyee._compat import CompatEventEmitter as EventEmitter +__all__ = ['BaseEventEmitter', 'EventEmitter', 'PyeeException'] -class PyeeException(Exception): - """An exception internal to pyee.""" +try: + from pyee._asyncio import AsyncIOEventEmitter # noqa + __all__.append('AsyncIOEventEmitter') +except ImportError: pass +try: + from pyee._twisted import TwistedEventEmitter # noqa + __all__.append('TwistedEventEmitter') +except ImportError: + 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(OrderedDict) - 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()``). - - In both the decorated and undecorated forms, the event handler is - returned. The upshot of this is that you can call decorated handlers - directly, as well as use them in remove_listener calls. - """ - - def _on(f): - self._add_event_handler(event, f, f) - return f - - if f is None: - return _on - else: - return _on(f) - - def _add_event_handler(self, event, k, v): - # Fire 'new_listener' *before* adding the new listener! - self.emit('new_listener', event, k) - - # Add the necessary function - # Note that k and v are the same for `on` handlers, but - # different for `once` handlers, where v is a wrapped version - # of k which removes itself before calling k - self._events[event][k] = v - - 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 - - for f in list(self._events[event].values()): - result = f(*args, **kwargs) - - # If f was a coroutine function, we need to schedule it and - # handle potential errors - if iscoroutine and iscoroutine(result): - if self._loop: - d = self._schedule(result, loop=self._loop) - else: - d = self._schedule(result) - - # scheduler gave us an asyncio Future - if hasattr(d, 'add_done_callback'): - @d.add_done_callback - def _callback(f): - exc = f.exception() - if exc: - self.emit('error', exc) - - # scheduler gave us a twisted Deferred - elif hasattr(d, 'addErrback'): - @d.addErrback - def _callback(exc): - self.emit('error', exc) - handled = True - - if not handled and event == 'error': - if 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 _wrapper(f): - def g(*args, **kwargs): - self.remove_listener(event, f) - # f may return a coroutine, so we need to return that - # result here so that emit can schedule it - return f(*args, **kwargs) - - self._add_event_handler(event, f, g) - return f - - if f is None: - return _wrapper - else: - return _wrapper(f) - - def remove_listener(self, event, f): - """Removes the function ``f`` from ``event``.""" - self._events[event].pop(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] = OrderedDict() - else: - self._events = defaultdict(OrderedDict) - - def listeners(self, event): - """Returns a list of all listeners registered to the ``event``. - """ - return list(self._events[event].keys()) +try: + from pyee._executor import ExecutorEventEmitter # noqa + __all__.append('ExecutorEventEmitter') +except ImportError: + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee/_asyncio.py new/pyee-6.0.0/pyee/_asyncio.py --- old/pyee-5.0.0/pyee/_asyncio.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/pyee/_asyncio.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +from asyncio import ensure_future, Future, iscoroutine +from pyee._base import BaseEventEmitter + +__all__ = ['AsyncIOEventEmitter'] + + +class AsyncIOEventEmitter(BaseEventEmitter): + """An event emitter class which can run asyncio coroutines in addition to + synchronous blocking functions. For example:: + + @ee.on('event') + async def async_handler(*args, **kwargs): + await returns_a_future() + + On emit, the event emitter will automatically schedule the coroutine using + ``asyncio.ensure_future`` and the configured event loop (defaults to + ``asyncio.get_event_loop()``). + + Unlike the case with the BaseEventEmitter, all exceptions raised by + event handlers are automatically emitted on the ``error`` event. This is + important for asyncio coroutines specifically but is also handled for + synchronous functions for consistency. + + When ``loop`` is specified, the supplied event loop will be used when + scheduling work with ``ensure_future``. Otherwise, the default asyncio + event loop is used. + + For asyncio 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. + """ + def __init__(self, loop=None): + super(AsyncIOEventEmitter, self).__init__() + self._loop = loop + + def _emit_run(self, f, args, kwargs): + try: + coro = f(*args, **kwargs) + except Exception as exc: + self.emit('error', exc) + else: + if iscoroutine(coro): + if self._loop: + f = ensure_future(coro, loop=self._loop) + else: + f = ensure_future(coro) + elif isinstance(coro, Future): + f = coro + else: + f = None + + if f: + @f.add_done_callback + def _callback(f): + exc = f.exception() + if exc: + self.emit('error', exc) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee/_base.py new/pyee-6.0.0/pyee/_base.py --- old/pyee-5.0.0/pyee/_base.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/pyee/_base.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +from collections import defaultdict, OrderedDict + +__all__ = ['BaseEventEmitter', 'PyeeException'] + + +class PyeeException(Exception): + """An exception internal to pyee.""" + pass + + +class BaseEventEmitter(object): + """The base event emitter class. All other event emitters inherit from + this class. + + Most events are registered with an emitter via the ``on`` and ``once`` + methods, and fired with the ``emit`` method. However, pyee event emitters + 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 on_error(message): + logging.err(message) + + ee.emit('error', Exception('something blew up')) + + All callbacks are handled in a synchronous, blocking manner. As in node.js, + raised exceptions are not automatically handled for you---you must catch + your own exceptions, and treat them accordingly. + """ + def __init__(self): + self._events = defaultdict(OrderedDict) + + def on(self, event, f=None): + """Registers the 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) + + In both the decorated and undecorated forms, the event handler is + returned. The upshot of this is that you can call decorated handlers + directly, as well as use them in remove_listener calls. + """ + + def _on(f): + self._add_event_handler(event, f, f) + return f + + if f is None: + return _on + else: + return _on(f) + + def _add_event_handler(self, event, k, v): + # Fire 'new_listener' *before* adding the new listener! + self.emit('new_listener', event, k) + + # Add the necessary function + # Note that k and v are the same for `on` handlers, but + # different for `once` handlers, where v is a wrapped version + # of k which removes itself before calling k + self._events[event][k] = v + + def _emit_run(self, f, args, kwargs): + f(*args, **kwargs) + + def _emit_handle_potential_error(self, event, error): + if event == 'error': + if error: + raise error + else: + raise PyeeException("Uncaught, unspecified 'error' event.") + + 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')'``. + """ + handled = False + + for f in list(self._events[event].values()): + self._emit_run(f, args, kwargs) + handled = True + + if not handled: + self._emit_handle_potential_error(event, args[0] if args else None) + + return handled + + def once(self, event, f=None): + """The same as ``ee.on``, except that the listener is automatically + removed after being called. + """ + def _wrapper(f): + def g(*args, **kwargs): + self.remove_listener(event, f) + # f may return a coroutine, so we need to return that + # result here so that emit can schedule it + return f(*args, **kwargs) + + self._add_event_handler(event, f, g) + return f + + if f is None: + return _wrapper + else: + return _wrapper(f) + + def remove_listener(self, event, f): + """Removes the function ``f`` from ``event``.""" + self._events[event].pop(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] = OrderedDict() + else: + self._events = defaultdict(OrderedDict) + + def listeners(self, event): + """Returns a list of all listeners registered to the ``event``. + """ + return list(self._events[event].keys()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee/_compat.py new/pyee-6.0.0/pyee/_compat.py --- old/pyee-5.0.0/pyee/_compat.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/pyee/_compat.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,68 @@ +from pyee._base import BaseEventEmitter +from warnings import warn + +try: + from asyncio import iscoroutine, ensure_future +except ImportError: + iscoroutine = None + ensure_future = None + + +class CompatEventEmitter(BaseEventEmitter): + """An EventEmitter exposed for compatibility with prior versions of + pyee. This functionality is deprecated; you should instead use either + ``AsyncIOEventEmitter``, ``TwistedEventEmitter``, ``ExecutorEventEmitter``, + or ``BaseEventEmitter``. + + This class is similar to the ``AsyncIOEventEmitter`` class, but also allows + for overriding the scheduler function (``ensure_future`` by default as in + ``ASyncIOEventEmitter``) and does duck typing checks to handle Deferreds. + In other words, by setting ``scheduler`` to + ``twisted.internet.defer.ensureDeferred`` this will support twisted use + cases for coroutines. + + When calling synchronous handlers, raised exceptions are ignored - as with + the BaseEventEmitter, you must capture and handle your own exceptions. + However, for coroutine functions, exceptions are handled by emitting them + on the ``error`` event. Note that when using with twisted, the ``error`` + event will emit Failures, not Exceptions. + + This class will also successfully import in python 2, but without coroutine + support. + """ + + def __init__(self, scheduler=ensure_future, loop=None): + warn(DeprecationWarning( + 'pyee.EventEmitter is deprecated and will be removed in a future ' + 'major version; you should instead use either ' + 'pyee.AsyncIOEventEmitter, pyee.TwistedEventEmitter, ' + 'pyee.ExecutorEventEmitter or pyee.BaseEventEmitter.' + )) + + super(CompatEventEmitter, self).__init__() + + self._schedule = scheduler + self._loop = loop + + def _emit_run(self, f, args, kwargs): + coro = f(*args, **kwargs) + + if iscoroutine and iscoroutine(coro): + if self._loop: + d = self._schedule(coro, loop=self._loop) + else: + d = self._schedule(coro) + + # scheduler gave us an asyncio Future + if hasattr(d, 'add_done_callback'): + @d.add_done_callback + def _callback(f): + exc = f.exception() + if exc: + self.emit('error', exc) + + # scheduler gave us a twisted Deferred + elif hasattr(d, 'addErrback'): + @d.addErrback + def _callback(exc): + self.emit('error', exc) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee/_executor.py new/pyee-6.0.0/pyee/_executor.py --- old/pyee-5.0.0/pyee/_executor.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/pyee/_executor.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +from pyee._base import BaseEventEmitter + +try: + from concurrent.futures import ThreadPoolExecutor +except ImportError: + from futures import ThreadPoolExecutor + +__all__ = ['ExecutorEventEmitter'] + + +class ExecutorEventEmitter(BaseEventEmitter): + """An event emitter class which runs handlers in a ``concurrent.futures`` + executor. If using python 2, this will fall back to trying to use the + ``futures`` backported library (caveats there apply). + + By default, this class creates a default ``ThreadPoolExecutor``, but + a custom executor may also be passed in explicitly to, for instance, + use a ``ProcessPoolExecutor`` instead. + + This class runs all emitted events on the configured executor. Errors + captured by the resulting Future are automatically emitted on the + ``error`` event. This is unlike the BaseEventEmitter, which have no error + handling. + + The underlying executor may be shut down by calling the ``shutdown`` + method. Alternately you can treat the event emitter as a context manager:: + + with ExecutorEventEmitter() as ee: + # Underlying executor open + + @ee.on('data') + def handler(data): + print(data) + + ee.emit('event') + + # Underlying executor closed + + Since the function call is scheduled on an executor, emit is always + non-blocking. + + No effort is made to ensure thread safety, beyond using an executor. + """ + def __init__(self, executor=None): + super(ExecutorEventEmitter, self).__init__() + if executor: + self._executor = executor + else: + self._executor = ThreadPoolExecutor() + + def _emit_run(self, f, args, kwargs): + future = self._executor.submit(f, *args, **kwargs) + + @future.add_done_callback + def _callback(f): + exc = f.exception() + if exc: + self.emit('error', exc) + + def shutdown(self, wait=True): + """Call ``shutdown`` on the internal executor.""" + + self._executor.shutdown(wait=wait) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.shutdown() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee/_twisted.py new/pyee-6.0.0/pyee/_twisted.py --- old/pyee-5.0.0/pyee/_twisted.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/pyee/_twisted.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from pyee._base import BaseEventEmitter + +from twisted.internet.defer import Deferred, ensureDeferred +from twisted.python.failure import Failure + +try: + from asyncio import iscoroutine +except ImportError: + iscoroutine = None + + +__all__ = ['TwistedEventEmitter'] + + +class TwistedEventEmitter(BaseEventEmitter): + """An event emitter class which can run twisted coroutines and handle + returned Deferreds, in addition to synchronous blocking functions. For + example:: + + @ee.on('event') + @inlineCallbacks + def async_handler(*args, **kwargs): + yield returns_a_deferred() + + or:: + + @ee.on('event') + async def async_handler(*args, **kwargs): + await returns_a_deferred() + + + When async handlers fail, Failures are first emitted on the ``failure`` + event. If there are no ``failure`` handlers, the Failure's associated + exception is then emitted on the ``error`` event. If there are no ``error`` + handlers, the exception is raised. For consistency, when handlers raise + errors synchronously, they're captured, wrapped in a Failure and treated + as an async failure. This is unlike the behavior of BaseEventEmitter, + which have no special error handling. + + For twisted 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. + + Similar behavior occurs for "sync" functions which return Deferreds. + """ + def __init__(self): + super(TwistedEventEmitter, self).__init__() + + def _emit_run(self, f, args, kwargs): + try: + result = f(*args, **kwargs) + except Exception: + self.emit('failure', Failure()) + else: + if iscoroutine and iscoroutine(result): + d = ensureDeferred(result) + elif isinstance(result, Deferred): + d = result + else: + d = None + if d: + @d.addErrback + def _errback(failure): + if failure: + self.emit('failure', failure) + + def _emit_handle_potential_error(self, event, error): + if event == 'failure': + self.emit('error', error.value) + else: + ( + super(TwistedEventEmitter, self) + )._emit_handle_potential_error(event, error) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee.egg-info/PKG-INFO new/pyee-6.0.0/pyee.egg-info/PKG-INFO --- old/pyee-5.0.0/pyee.egg-info/PKG-INFO 2017-11-18 23:44:51.000000000 +0100 +++ new/pyee-6.0.0/pyee.egg-info/PKG-INFO 2019-04-13 19:50:13.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyee -Version: 5.0.0 +Version: 6.0.0 Summary: A port of node.js's EventEmitter to python. Home-page: https://github.com/jfhbrook/pyee Author: Joshua Holbrook @@ -14,8 +14,10 @@ .. 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. + pyee supplies a ``BaseEventEmitter`` object that is similar to the + ``EventEmitter`` class from Node.js. It also supplies a number of subclasses + with added support for async and threaded programming in python, such as + async/await as seen in python 3.5+. Docs: ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/pyee.egg-info/SOURCES.txt new/pyee-6.0.0/pyee.egg-info/SOURCES.txt --- old/pyee-5.0.0/pyee.egg-info/SOURCES.txt 2017-11-18 23:44:51.000000000 +0100 +++ new/pyee-6.0.0/pyee.egg-info/SOURCES.txt 2019-04-13 19:50:13.000000000 +0200 @@ -7,10 +7,17 @@ setup.py version.txt pyee/__init__.py +pyee/_asyncio.py +pyee/_base.py +pyee/_compat.py +pyee/_executor.py +pyee/_twisted.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.py \ No newline at end of file +tests/test_executor.py +tests/test_sync.py +tests/test_twisted.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/setup.cfg new/pyee-6.0.0/setup.cfg --- old/pyee-5.0.0/setup.cfg 2017-11-18 23:44:51.000000000 +0100 +++ new/pyee-6.0.0/setup.cfg 2019-04-13 19:50:13.000000000 +0200 @@ -4,5 +4,4 @@ [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/setup.py new/pyee-6.0.0/setup.py --- old/pyee-5.0.0/setup.py 2017-11-18 01:14:03.000000000 +0100 +++ new/pyee-6.0.0/setup.py 2019-04-13 19:47:29.000000000 +0200 @@ -18,7 +18,7 @@ 'pytest-asyncio; python_version > "3.4"', 'vcversioner' ], - tests_require=['twisted'], + tests_require=['twisted', 'futures; python_version < "3.0"'], include_package_data=True, description="A port of node.js's EventEmitter to python.", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/tests/test_async.py new/pyee-6.0.0/tests/test_async.py --- old/pyee-5.0.0/tests/test_async.py 2017-11-18 01:03:16.000000000 +0100 +++ new/pyee-6.0.0/tests/test_async.py 2019-04-13 19:47:29.000000000 +0200 @@ -1,26 +1,30 @@ # -*- coding: utf-8 -*- import pytest -import pytest_asyncio.plugin +import pytest_asyncio.plugin # noqa -from asyncio import Future, gather, new_event_loop, wait_for +from asyncio import Future, wait_for from mock import Mock from twisted.internet.defer import ensureDeferred, succeed -from pyee import EventEmitter +from pyee import EventEmitter, AsyncIOEventEmitter, TwistedEventEmitter class PyeeTestError(Exception): pass +@pytest.mark.parametrize('cls', [ + AsyncIOEventEmitter, + EventEmitter +]) @pytest.mark.asyncio -async def test_asyncio_emit(event_loop): - """Test that event_emitters can handle wrapping coroutines as used with - asyncio. +async def test_asyncio_emit(cls, event_loop): + """Test that asyncio-supporting event emitters can handle wrapping + coroutines """ - ee = EventEmitter(loop=event_loop) + ee = cls(loop=event_loop) should_call = Future(loop=event_loop) @@ -32,15 +36,20 @@ result = await wait_for(should_call, 0.1) - assert result == True + assert result is True +@pytest.mark.parametrize('cls', [ + AsyncIOEventEmitter, + EventEmitter +]) @pytest.mark.asyncio -async def test_asyncio_once_emit(event_loop): - """Test that event_emitters also wrap coroutines when using once +async def test_asyncio_once_emit(cls, event_loop): + """Test that asyncio-supporting event emitters also wrap coroutines when + using once """ - ee = EventEmitter(loop=event_loop) + ee = cls(loop=event_loop) should_call = Future(loop=event_loop) @@ -52,15 +61,19 @@ result = await wait_for(should_call, 0.1) - assert result == True + assert result is True +@pytest.mark.parametrize('cls', [ + AsyncIOEventEmitter, + EventEmitter +]) @pytest.mark.asyncio -async def test_asyncio_error(event_loop): - """Test that event_emitters can handle errors when wrapping coroutines as - used with asyncio. +async def test_asyncio_error(cls, event_loop): + """Test that asyncio-supporting event emitters can handle errors when + wrapping coroutines """ - ee = EventEmitter(loop=event_loop) + ee = cls(loop=event_loop) should_call = Future(loop=event_loop) @@ -70,7 +83,6 @@ @ee.on('error') def handle_error(exc): - assert isinstance(exc, PyeeTestError) should_call.set_result(exc) ee.emit('event') @@ -80,11 +92,38 @@ assert isinstance(result, PyeeTestError) -def test_twisted_emit(): - """Test that event_emitters can handle wrapping coroutines when using - twisted and ensureDeferred. +@pytest.mark.asyncio +async def test_sync_error(event_loop): + """Test that regular functions have the same error handling as coroutines + """ + ee = AsyncIOEventEmitter(loop=event_loop) + + should_call = Future(loop=event_loop) + + @ee.on('event') + def sync_handler(): + raise PyeeTestError() + + @ee.on('error') + def handle_error(exc): + should_call.set_result(exc) + + ee.emit('event') + + result = await wait_for(should_call, 0.1) + + assert isinstance(result, PyeeTestError) + + +@pytest.mark.parametrize('cls,kwargs', [ + (TwistedEventEmitter, dict()), + (EventEmitter, dict(scheduler=ensureDeferred)) +]) +def test_twisted_emit(cls, kwargs): + """Test that twisted-supporting event emitters can handle wrapping + coroutines """ - ee = EventEmitter(scheduler=ensureDeferred) + ee = cls(**kwargs) should_call = Mock() @@ -98,11 +137,15 @@ should_call.assert_called_once() -def test_twisted_once(): - """Test that event_emitters also wrap coroutines for once when using - twisted and ensureDeferred. +@pytest.mark.parametrize('cls,kwargs', [ + (TwistedEventEmitter, dict()), + (EventEmitter, dict(scheduler=ensureDeferred)) +]) +def test_twisted_once(cls, kwargs): + """Test that twisted-supporting event emitters also wrap coroutines for + once """ - ee = EventEmitter(scheduler=ensureDeferred) + ee = cls(**kwargs) should_call = Mock() @@ -117,10 +160,9 @@ def test_twisted_error(): - """Test that event_emitters can handle wrapping coroutines when using - twisted and ensureDeferred. + """Test that TwistedEventEmitters handle Failures when wrapping coroutines. """ - ee = EventEmitter(scheduler=ensureDeferred) + ee = TwistedEventEmitter() should_call = Mock() @@ -128,7 +170,7 @@ async def event_handler(): raise PyeeTestError() - @ee.on('error') + @ee.on('failure') def handle_error(e): should_call(e) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/tests/test_executor.py new/pyee-6.0.0/tests/test_executor.py --- old/pyee-5.0.0/tests/test_executor.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/tests/test_executor.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from mock import Mock +from time import sleep + +from pyee import ExecutorEventEmitter + + +class PyeeTestError(Exception): + pass + + +def test_executor_emit(): + """Test that ExecutorEventEmitters can emit events. + """ + with ExecutorEventEmitter() as ee: + should_call = Mock() + + @ee.on('event') + def event_handler(): + should_call(True) + + ee.emit('event') + sleep(0.1) + + should_call.assert_called_once() + + +def test_executor_once(): + """Test that ExecutorEventEmitters also emit events for once. + """ + with ExecutorEventEmitter() as ee: + should_call = Mock() + + @ee.once('event') + def event_handler(): + should_call(True) + + ee.emit('event') + sleep(0.1) + + should_call.assert_called_once() + + +def test_executor_error(): + """Test that ExecutorEventEmitters handle errors. + """ + with ExecutorEventEmitter() as ee: + should_call = Mock() + + @ee.on('event') + def event_handler(): + raise PyeeTestError() + + @ee.on('error') + def handle_error(e): + should_call(e) + + ee.emit('event') + + sleep(0.1) + + should_call.assert_called_once() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/tests/test_sync.py new/pyee-6.0.0/tests/test_sync.py --- old/pyee-5.0.0/tests/test_sync.py 2017-11-18 23:19:26.000000000 +0100 +++ new/pyee-6.0.0/tests/test_sync.py 2019-04-13 19:47:29.000000000 +0200 @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pytest from inspect import getmro from collections import OrderedDict @@ -6,16 +7,22 @@ from mock import Mock from pytest import raises -from pyee import EventEmitter +from pyee import BaseEventEmitter, EventEmitter + class PyeeTestException(Exception): pass -def test_emit_sync(): + +@pytest.mark.parametrize('cls', [ + BaseEventEmitter, + EventEmitter +]) +def test_emit_sync(cls): """Basic synchronous emission works""" call_me = Mock() - ee = EventEmitter() + ee = cls() @ee.on('event') def event_handler(data, **kwargs): @@ -28,15 +35,19 @@ call_me.assert_called_once() -def test_emit_error(): +@pytest.mark.parametrize('cls', [ + BaseEventEmitter, + EventEmitter +]) +def test_emit_error(cls): """Errors raise with no event handler, otherwise emit on handler""" call_me = Mock() - ee = EventEmitter() + ee = cls() test_exception = PyeeTestException('lololol') - with raises(PyeeTestException) as exc_info: + with raises(PyeeTestException): ee.emit('error', test_exception) @ee.on('error') @@ -44,7 +55,7 @@ call_me() # No longer raises and error instead return True indicating handled - assert ee.emit('error', test_exception) + assert ee.emit('error', test_exception) is True call_me.assert_called_once() @@ -54,7 +65,7 @@ """ call_me = Mock() - ee = EventEmitter() + ee = BaseEventEmitter() # make sure emitting without a callback returns False assert not ee.emit('data') @@ -70,7 +81,7 @@ """The 'new_listener' event fires whenever a new listerner is added.""" call_me = Mock() - ee = EventEmitter() + ee = BaseEventEmitter() ee.on('new_listener', call_me) @@ -85,7 +96,7 @@ def test_listener_removal(): """Removing listeners removes the correct listener from an event.""" - ee = EventEmitter() + ee = BaseEventEmitter() # Some functions to pass to the EE def first(): @@ -122,7 +133,10 @@ ]) ee.remove_listener('event', first) - assert ee._events['event'] == OrderedDict([(third, third), (fourth, fourth)]) + assert ee._events['event'] == OrderedDict([ + (third, third), + (fourth, fourth) + ]) ee.remove_all_listeners('event') assert ee._events['event'] == OrderedDict() @@ -134,7 +148,7 @@ """ call_me = Mock() - ee = EventEmitter() + ee = BaseEventEmitter() def should_remove(): ee.remove_listener('remove', call_me) @@ -149,7 +163,7 @@ call_me.reset_mock() # Also test with the listeners added in the opposite order - ee = EventEmitter() + ee = BaseEventEmitter() ee.on('remove', call_me) ee.on('remove', should_remove) @@ -166,14 +180,14 @@ # gets removed afterwards call_me = Mock() - ee = EventEmitter() + ee = BaseEventEmitter() 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) + ee.once('event', once_handler) ee.emit('event', 'emitter is emitted!') @@ -186,7 +200,7 @@ """Removal of once functions works """ - ee = EventEmitter() + ee = BaseEventEmitter() def once_handler(data): pass @@ -204,7 +218,7 @@ """`listeners()` returns a copied list of listeners.""" call_me = Mock() - ee = EventEmitter() + ee = BaseEventEmitter() @ee.on('event') def event_handler(): @@ -232,7 +246,7 @@ call_me = Mock() call_me_also = Mock() - ee = EventEmitter() + ee = BaseEventEmitter() @ee.on('always') def always_event_handler(): @@ -262,18 +276,19 @@ def test_inheritance(): """Test that inheritance is preserved from object""" - assert object in getmro(EventEmitter) + assert object in getmro(BaseEventEmitter) - class example(EventEmitter): + class example(BaseEventEmitter): def __init__(self): super(example, self).__init__() - assert EventEmitter in getmro(example) + assert BaseEventEmitter in getmro(example) assert object in getmro(example) + def test_multiple_inheritance(): """Test that inheritance is preserved along a lengthy MRO""" - class example(EventEmitter): + class example(BaseEventEmitter): def __init__(self): super(example, self).__init__() @@ -289,4 +304,4 @@ def __init__(self): super(_example2, self).__init__() - a = _example2() + a = _example2() # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/tests/test_twisted.py new/pyee-6.0.0/tests/test_twisted.py --- old/pyee-5.0.0/tests/test_twisted.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pyee-6.0.0/tests/test_twisted.py 2019-04-13 19:47:29.000000000 +0200 @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +from mock import Mock +from twisted.internet.defer import inlineCallbacks +from twisted.python.failure import Failure + +from pyee import TwistedEventEmitter + + +class PyeeTestError(Exception): + pass + + +def test_propagates_failure(): + """Test that TwistedEventEmitters can propagate failures + from twisted Deferreds + """ + ee = TwistedEventEmitter() + + should_call = Mock() + + @ee.on('event') + @inlineCallbacks + def event_handler(): + yield Failure(PyeeTestError()) + + @ee.on('failure') + def handle_failure(f): + assert isinstance(f, Failure) + should_call(f) + + ee.emit('event') + + should_call.assert_called_once() + + +def test_propagates_sync_failure(): + """Test that TwistedEventEmitters can propagate failures + from twisted Deferreds + """ + ee = TwistedEventEmitter() + + should_call = Mock() + + @ee.on('event') + def event_handler(): + raise PyeeTestError() + + @ee.on('failure') + def handle_failure(f): + assert isinstance(f, Failure) + should_call(f) + + ee.emit('event') + + should_call.assert_called_once() + + +def test_propagates_exception(): + """Test that TwistedEventEmitters propagate failures as exceptions to + the error event when no failure handler + """ + + ee = TwistedEventEmitter() + + should_call = Mock() + + @ee.on('event') + @inlineCallbacks + def event_handler(): + yield Failure(PyeeTestError()) + + @ee.on('error') + def handle_error(exc): + assert isinstance(exc, Exception) + should_call(exc) + + ee.emit('event') + + should_call.assert_called_once() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyee-5.0.0/version.txt new/pyee-6.0.0/version.txt --- old/pyee-5.0.0/version.txt 2017-11-18 23:44:51.000000000 +0100 +++ new/pyee-6.0.0/version.txt 2019-04-13 19:50:13.000000000 +0200 @@ -1 +1 @@ -5.0.0-0-ga33a635 \ No newline at end of file +6.0.0-0-gad0107d \ No newline at end of file