[Python-modules-commits] [python-signaller] 01/03: importing python-signaller_1.1.0.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Wed Jul 20 08:50:59 UTC 2016
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch master
in repository python-signaller.
commit ef9cd3009e99781edfaf9b8b8154104bce0e9d90
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Wed Jul 20 09:47:10 2016 +0200
importing python-signaller_1.1.0.orig.tar.gz
---
LICENSE | 21 ++++
MANIFEST.in | 2 +
PKG-INFO | 18 ++++
README.rst | 95 ++++++++++++++++++
Signaller.egg-info/PKG-INFO | 18 ++++
Signaller.egg-info/SOURCES.txt | 9 ++
Signaller.egg-info/dependency_links.txt | 1 +
Signaller.egg-info/top_level.txt | 1 +
setup.cfg | 5 +
setup.py | 27 ++++++
signaller.py | 164 ++++++++++++++++++++++++++++++++
11 files changed, 361 insertions(+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6999417
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The 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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..9d5d250
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include LICENSE
+include README.rst
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..959b759
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,18 @@
+Metadata-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 at 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
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..3d8be4c
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,95 @@
+Signaller
+=========
+
+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 <Signal 'sig_test' at 0x7f3c468bfc50> to slot <function slot at 0x7f3c46cc6f28>
+ INFO:signaller:Connecting signal <Signal 'sig_test' at 0x7f3c468bfc50> to slot <function <lambda> at 0x7f3c468c97b8>
+ INFO:signaller:Connecting signal <Signal 'sig_test' at 0x7f3c468bfc50> to slot <function slot2 at 0x7f3c43c9e400>
+ INFO:signaller:Connecting signal <Signal 'sig_test' at 0x7f3c468bfc50> to slot <function slot3 at 0x7f3c43c9e598>
+ DEBUG:signaller:Marking instance method <function Cls.slot4 at 0x7f3c43c9e6a8> for autoconnect to signal <Signal 'sig_test' at 0x7f3c468bfc50>
+ INFO:signaller:Connecting signal <Signal 'sig_test' at 0x7f3c468bfc50> to slot <bound method Cls.slot4 of <__main__.Cls object at 0x7f3c43f11d30>>
+ DEBUG:signaller:Object <function slot at 0x7f3c46cc6f28> has been deleted
+ INFO:signaller:Disconnecting slot <Reference (weak) to <function slot at 0x7f3c46cc6f28> (dead)> from signal <Signal 'sig_test' at 0x7f3c468bfc50>
+ INFO:signaller:Disconnecting slot <function slot2 at 0x7f3c43c9e400> from signal <Signal 'sig_test' at 0x7f3c468bfc50>
+ INFO:signaller:Emitting signal <Signal 'sig_test' at 0x7f3c468bfc50>
+ DEBUG:signaller:Calling slot <Reference (weak) to <function slot3 at 0x7f3c43c9e598>> asynchronously (in executor <concurrent.futures.thread.ThreadPoolExecutor object at 0x7f3c468bff28>)
+ slot3: Hello world!
+ DEBUG:signaller:Calling slot <Reference (strong) to <function <lambda> at 0x7f3c468c97b8>>
+ slot_lambda: Hello world!
+ DEBUG:signaller:Calling slot <Reference (weak) to <bound method Cls.slot4 of <__main__.Cls object at 0x7f3c43f11d30>>>
+ slot4: Hello world!
diff --git a/Signaller.egg-info/PKG-INFO b/Signaller.egg-info/PKG-INFO
new file mode 100644
index 0000000..959b759
--- /dev/null
+++ b/Signaller.egg-info/PKG-INFO
@@ -0,0 +1,18 @@
+Metadata-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 at 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
diff --git a/Signaller.egg-info/SOURCES.txt b/Signaller.egg-info/SOURCES.txt
new file mode 100644
index 0000000..68feaf1
--- /dev/null
+++ b/Signaller.egg-info/SOURCES.txt
@@ -0,0 +1,9 @@
+LICENSE
+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.txt
\ No newline at end of file
diff --git a/Signaller.egg-info/dependency_links.txt b/Signaller.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/Signaller.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/Signaller.egg-info/top_level.txt b/Signaller.egg-info/top_level.txt
new file mode 100644
index 0000000..3d62b50
--- /dev/null
+++ b/Signaller.egg-info/top_level.txt
@@ -0,0 +1 @@
+signaller
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..bd4a020
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+#!/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 at 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',
+ ]
+)
diff --git a/signaller.py b/signaller.py
new file mode 100644
index 0000000..dc10a48
--- /dev/null
+++ b/signaller.py
@@ -0,0 +1,164 @@
+"""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 '<Reference ({}) to {}{}>'.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 '<Signal {} at {}>'.format(
+ '\'{}\''.format(self.name) if self.name else '<anonymous>',
+ hex(id(self))
+ )
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-signaller.git
More information about the Python-modules-commits
mailing list