Signaller-1.1.0/0000755000175000017500000000000012654244040013720 5ustar mikosmikos00000000000000Signaller-1.1.0/Signaller.egg-info/0000755000175000017500000000000012654244040017332 5ustar mikosmikos00000000000000Signaller-1.1.0/Signaller.egg-info/PKG-INFO0000644000175000017500000000131512654244032020430 0ustar mikosmikos00000000000000Metadata-Version: 1.1 Name: Signaller Version: 1.1.0 Summary: Signals and slots implementation with asyncio support Home-page: https://github.com/xmikos/signaller Author: Michal Krenek (Mikos) Author-email: m.krenek@gmail.com License: MIT Description: UNKNOWN Keywords: signal slot dispatch dispatcher observer event notify asyncio weakref Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Signaller-1.1.0/Signaller.egg-info/SOURCES.txt0000644000175000017500000000027012654244032021216 0ustar mikosmikos00000000000000LICENSE MANIFEST.in README.rst setup.py signaller.py Signaller.egg-info/PKG-INFO Signaller.egg-info/SOURCES.txt Signaller.egg-info/dependency_links.txt Signaller.egg-info/top_level.txtSignaller-1.1.0/Signaller.egg-info/dependency_links.txt0000644000175000017500000000000112654244032023401 0ustar mikosmikos00000000000000 Signaller-1.1.0/Signaller.egg-info/top_level.txt0000644000175000017500000000001212654244032022056 0ustar mikosmikos00000000000000signaller Signaller-1.1.0/LICENSE0000644000175000017500000000207012652623227014732 0ustar mikosmikos00000000000000The MIT License (MIT) Copyright (c) 2016 Michal Krenek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Signaller-1.1.0/MANIFEST.in0000644000175000017500000000004312652622774015467 0ustar mikosmikos00000000000000include LICENSE include README.rst Signaller-1.1.0/README.rst0000644000175000017500000000772712654131600015421 0ustar mikosmikos00000000000000Signaller ========= Signals and slots implementation with asyncio support Slots can be functions, methods or coroutines. Weak references are used by default. If slot is coroutine, it will be scheduled to run asynchronously with ``asyncio.async()`` (but you must run event loop by yourself). You can also run blocking functions asynchronously by specifying ``force_async=True`` when connecting signal to slot (it will only apply to that slot) or when creating signal (it will apply to all connected slots). ThreadPoolExecutor with 5 worker threads is used by default, but it can be changed when creating signal with ``executor`` argument. Requirements ------------ - Python >= 3.4 Usage ----- Example: .. code-block:: python import logging from signaller import Signal, autoconnect # Enable verbose logging logging.basicConfig(level=logging.DEBUG) # Creating signals (you can set signal name, but it is not required, # signals can be anonymous): sig_test = Signal('sig_test') # Connecting signals to slots (uses weak references by default, # but you can force strong references by specifying weak=False): def slot(arg): print('slot:', arg) sig_test.connect(slot) sig_test.connect(lambda arg: print('slot_lambda:', arg), weak=False) # You can also use decorators for connecting signals to slots: @sig_test.connect def slot2(arg): print('slot2:', arg) # And keyword arguments can be specified when using decorators too: @sig_test.connect(force_async=True) def slot3(arg): print('slot3:', arg) # You can also use decorators on methods, then signals will be connected to instance # methods automatically whenever new instance is created. But you must decorate class # with @autoconnect decorator for autoconnection to work. Class methods and # static methods are not supported. @autoconnect class Cls: @sig_test.connect def slot4(self, arg): print('slot4:', arg) obj = Cls() # Slots are automatically disconnected from signals # when using weak references: del slot # Or you can disconnect slots manually: sig_test.disconnect(slot2) # Emitting signals (you can send both positional and keyword # arguments to connected slots): sig_test.emit('Hello world!') Output:: INFO:signaller:Connecting signal to slot INFO:signaller:Connecting signal to slot at 0x7f3c468c97b8> INFO:signaller:Connecting signal to slot INFO:signaller:Connecting signal to slot DEBUG:signaller:Marking instance method for autoconnect to signal INFO:signaller:Connecting signal to slot > DEBUG:signaller:Object has been deleted INFO:signaller:Disconnecting slot (dead)> from signal INFO:signaller:Disconnecting slot from signal INFO:signaller:Emitting signal DEBUG:signaller:Calling slot > asynchronously (in executor ) slot3: Hello world! DEBUG:signaller:Calling slot at 0x7f3c468c97b8>> slot_lambda: Hello world! DEBUG:signaller:Calling slot >> slot4: Hello world! Signaller-1.1.0/setup.py0000755000175000017500000000152012654235713015442 0ustar mikosmikos00000000000000#!/usr/bin/env python import sys from setuptools import setup setup( name='Signaller', version='1.1.0', description='Signals and slots implementation with asyncio support', author='Michal Krenek (Mikos)', author_email='m.krenek@gmail.com', url='https://github.com/xmikos/signaller', license='MIT', py_modules=['signaller'], install_requires=[], keywords='signal slot dispatch dispatcher observer event notify asyncio weakref', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) Signaller-1.1.0/signaller.py0000644000175000017500000001366712654122765016300 0ustar mikosmikos00000000000000"""Signals and slots implementation with asyncio support Slots can be functions, methods or coroutines. Weak references are used by default. If slot is coroutine, it will be scheduled to run asynchronously with ``asyncio.async()`` (but you must run event loop by yourself). You can also run blocking functions asynchronously by specifying ``force_async=True`` when connecting signal to slot (it will only apply to that slot) or when creating signal (it will apply to all connected slots). ThreadPoolExecutor with 5 worker threads is used by default, but it can be changed when creating signal with ``executor`` argument. """ import asyncio, concurrent.futures, weakref, inspect, logging from functools import wraps logger = logging.getLogger(__name__) def autoconnect(cls): """Class decorator for automatically connecting instance methods to signals""" old_init = cls.__init__ @wraps(old_init) def new_init(self, *args, **kwargs): for name, method in inspect.getmembers(self, predicate=inspect.ismethod): if hasattr(method, '_signals'): for sig, sig_kwargs in method._signals.items(): sig.connect(method, **sig_kwargs) old_init(self, *args, **kwargs) cls.__init__ = new_init return cls class Reference: """Weak or strong reference to function or method""" def __init__(self, obj, callback=None, weak=True, force_async=False): if not callable(obj): raise TypeError('obj has to be callable') self.force_async = force_async self._weak = weak self._alive = True self._hash = obj.__hash__() self._repr = obj.__repr__() if self.weak: if inspect.ismethod(obj): self._ref = weakref.WeakMethod(obj, self._wrap_callback(callback)) else: self._ref = weakref.ref(obj, self._wrap_callback(callback)) else: self._ref = obj def _wrap_callback(self, callback): """Wrap callback to be called with reference to ourselves, not underlying weakref object""" def wrapper(obj): logger.debug('Object {} has been deleted'.format(self._repr)) self._alive = False if callback is not None: return callback(self) return wrapper @property def weak(self): """Returns True if this is weak reference""" return self._weak @property def alive(self): """Returns True if underlying weak reference is still alive""" return self._alive def getobject(self): """Returns underlying object""" return self._ref() if self.weak else self._ref def __call__(self, *args, **kwargs): return self.getobject()(*args, **kwargs) def __hash__(self): return self._hash def __eq__(self, other): return self.__hash__() == other.__hash__() def __repr__(self): return ''.format( 'weak' if self.weak else 'strong', self._repr, ' (dead)' if not self.alive else '' ) class Signal: """Signal emitter""" def __init__(self, name='', loop=None, force_async=False, executor=None): self.name = name self.loop = loop self.force_async = force_async self.executor = executor or concurrent.futures.ThreadPoolExecutor(max_workers=5) self._slots = set() def emit(self, *args, **kwargs): """Emit signal (call all connected slots)""" logger.info('Emitting signal {}'.format(self)) for ref in self._slots: if asyncio.iscoroutinefunction(ref.getobject()): logger.debug('Scheduling coroutine {}'.format(ref)) asyncio.async(ref(*args, **kwargs), loop=self.loop) else: if self.force_async or ref.force_async: logger.debug('Calling slot {} asynchronously (in executor {})'.format( ref, self.executor )) self.executor.submit(ref, *args, **kwargs) else: logger.debug('Calling slot {}'.format(ref)) ref(*args, **kwargs) def clear(self): """Disconnect all slots""" logger.info('Disconnecting all slots from signal {}'.format(self)) self._slots.clear() def connect(self, *args, weak=True, force_async=False): """Connect signal to slot (can be also used as decorator)""" def wrapper(func): args = inspect.getfullargspec(func).args if inspect.isfunction(func) and args and args[0] == 'self': logger.debug('Marking instance method {} for autoconnect to signal {}'.format( func, self )) if not hasattr(func, '_signals'): func._signals = {} func._signals[self] = {'weak': weak, 'force_async': force_async} else: logger.info('Connecting signal {} to slot {}'.format(self, func)) self._slots.add( Reference(func, callback=self.disconnect, weak=weak, force_async=force_async) ) return func # If there is one (and only one) positional argument and this argument is callable, # assume it is the decorator (without any optional keyword arguments) if len(args) == 1 and callable(args[0]): return wrapper(args[0]) else: return wrapper def disconnect(self, slot): """Disconnect slot from signal""" try: logger.info('Disconnecting slot {} from signal {}'.format(slot, self)) self._slots.remove(slot) except KeyError: logger.warning('Slot {} is not connected!'.format(slot)) pass def __repr__(self): return ''.format( '\'{}\''.format(self.name) if self.name else '', hex(id(self)) ) Signaller-1.1.0/PKG-INFO0000644000175000017500000000131512654244040015015 0ustar mikosmikos00000000000000Metadata-Version: 1.1 Name: Signaller Version: 1.1.0 Summary: Signals and slots implementation with asyncio support Home-page: https://github.com/xmikos/signaller Author: Michal Krenek (Mikos) Author-email: m.krenek@gmail.com License: MIT Description: UNKNOWN Keywords: signal slot dispatch dispatcher observer event notify asyncio weakref Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries :: Python Modules Signaller-1.1.0/setup.cfg0000644000175000017500000000007312654244040015541 0ustar mikosmikos00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0