[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