/usr/lib/python3/dist-packages/signaller.py is in python3-signaller 1.1.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 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))
)
|