python-dispatch 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydispatch/__init__.py +55 -1
- pydispatch/aioutils.py +33 -7
- pydispatch/decorators.py +135 -0
- pydispatch/dispatch.py +122 -14
- pydispatch/utils.py +13 -4
- {python_dispatch-0.2.1.dist-info → python_dispatch-0.2.3.dist-info}/METADATA +16 -16
- python_dispatch-0.2.3.dist-info/RECORD +9 -0
- python_dispatch-0.2.3.dist-info/WHEEL +4 -0
- python_dispatch-0.2.1.dist-info/LICENSE.txt +0 -21
- python_dispatch-0.2.1.dist-info/RECORD +0 -10
- python_dispatch-0.2.1.dist-info/WHEEL +0 -5
- python_dispatch-0.2.1.dist-info/top_level.txt +0 -1
pydispatch/__init__.py
CHANGED
|
@@ -15,5 +15,59 @@ if sys.version_info < (3, 6): # pragma: no cover
|
|
|
15
15
|
'After version 0.1.x, `python-dispatch` will only support Python 3.6 or greater.',
|
|
16
16
|
UserWarning)
|
|
17
17
|
|
|
18
|
-
from pydispatch.dispatch import
|
|
18
|
+
from pydispatch.dispatch import *
|
|
19
|
+
from pydispatch.dispatch import _GLOBAL_DISPATCHER
|
|
19
20
|
from pydispatch.properties import *
|
|
21
|
+
from pydispatch import decorators
|
|
22
|
+
from pydispatch.decorators import *
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def register_event(*names):
|
|
26
|
+
"""Register event (or events) on the :ref:`global-dispatcher`
|
|
27
|
+
|
|
28
|
+
.. seealso:: :meth:`.Dispatcher.register_event`
|
|
29
|
+
.. versionadded:: 0.2.2
|
|
30
|
+
"""
|
|
31
|
+
_GLOBAL_DISPATCHER.register_event(*names)
|
|
32
|
+
decorators._post_register_hook(*names)
|
|
33
|
+
|
|
34
|
+
def bind(**kwargs):
|
|
35
|
+
"""Subscribe callbacks to events on the :ref:`global-dispatcher`
|
|
36
|
+
|
|
37
|
+
.. seealso:: :meth:`.Dispatcher.bind`
|
|
38
|
+
.. versionadded:: 0.2.2
|
|
39
|
+
"""
|
|
40
|
+
_GLOBAL_DISPATCHER.bind(**kwargs)
|
|
41
|
+
|
|
42
|
+
def unbind(*args):
|
|
43
|
+
"""Unbind callbacks from events on the :ref:`global-dispatcher`
|
|
44
|
+
|
|
45
|
+
.. seealso:: :meth:`.Dispatcher.unbind`
|
|
46
|
+
.. versionadded:: 0.2.2
|
|
47
|
+
"""
|
|
48
|
+
_GLOBAL_DISPATCHER.unbind(*args)
|
|
49
|
+
|
|
50
|
+
def bind_async(loop, **kwargs):
|
|
51
|
+
"""Bind async callbacks to events on the :ref:`global-dispatcher`
|
|
52
|
+
|
|
53
|
+
.. seealso:: :meth:`.Dispatcher.bind_async`
|
|
54
|
+
.. versionadded:: 0.2.2
|
|
55
|
+
"""
|
|
56
|
+
_GLOBAL_DISPATCHER.bind_async(loop, **kwargs)
|
|
57
|
+
|
|
58
|
+
def emit(name, *args, **kwargs):
|
|
59
|
+
"""Dispatch the event with the given *name* on the :ref:`global-dispatcher`
|
|
60
|
+
|
|
61
|
+
.. seealso:: :meth:`.Dispatcher.emit`
|
|
62
|
+
.. versionadded:: 0.2.2
|
|
63
|
+
"""
|
|
64
|
+
return _GLOBAL_DISPATCHER.emit(name, *args, **kwargs)
|
|
65
|
+
|
|
66
|
+
def get_dispatcher_event(name):
|
|
67
|
+
"""Retrieve the :class:`~.dispatch.Event` object by the given name
|
|
68
|
+
from the :ref:`global-dispatcher`
|
|
69
|
+
|
|
70
|
+
.. seealso:: :meth:`.Dispatcher.get_dispatcher_event`
|
|
71
|
+
.. versionadded:: 0.2.2
|
|
72
|
+
"""
|
|
73
|
+
return _GLOBAL_DISPATCHER.get_dispatcher_event(name)
|
pydispatch/aioutils.py
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import asyncio
|
|
2
3
|
import threading
|
|
3
|
-
from
|
|
4
|
-
from
|
|
4
|
+
from weakref import ref
|
|
5
|
+
from _weakref import _remove_dead_weakref # type: ignore[import]
|
|
6
|
+
if sys.version_info < (3, 14):
|
|
7
|
+
from _weakrefset import _IterationGuard
|
|
8
|
+
else:
|
|
9
|
+
# _IterationGuard was removed in Python 3.14. We define a no-op version here
|
|
10
|
+
class _IterationGuard:
|
|
11
|
+
def __init__(self, obj):
|
|
12
|
+
pass
|
|
13
|
+
def __enter__(self):
|
|
14
|
+
pass
|
|
15
|
+
def __exit__(self, *args):
|
|
16
|
+
pass
|
|
17
|
+
|
|
5
18
|
|
|
6
19
|
from pydispatch.utils import (
|
|
7
20
|
WeakMethodContainer,
|
|
21
|
+
isfunction,
|
|
8
22
|
get_method_vars,
|
|
9
|
-
_remove_dead_weakref,
|
|
10
23
|
)
|
|
11
24
|
|
|
12
25
|
|
|
@@ -191,6 +204,12 @@ class AioWeakMethodContainer(WeakMethodContainer):
|
|
|
191
204
|
_remove_dead_weakref(self.data, wr.key)
|
|
192
205
|
self._on_weakref_fin(wr.key)
|
|
193
206
|
self._remove = remove
|
|
207
|
+
|
|
208
|
+
# `_pending_removals` and `_iterating` were removed in Python 3.14.
|
|
209
|
+
# To maintain compatibility with earlier versions, we reintroduce them here.
|
|
210
|
+
self._pending_removals = []
|
|
211
|
+
self._iterating = set()
|
|
212
|
+
|
|
194
213
|
self.event_loop_map = {}
|
|
195
214
|
def add_method(self, loop, callback):
|
|
196
215
|
"""Add a coroutine function
|
|
@@ -200,9 +219,13 @@ class AioWeakMethodContainer(WeakMethodContainer):
|
|
|
200
219
|
on which to schedule callbacks
|
|
201
220
|
callback: The :term:`coroutine function` to add
|
|
202
221
|
"""
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
if isfunction(callback):
|
|
223
|
+
wrkey = ('function', id(callback))
|
|
224
|
+
self[wrkey] = callback
|
|
225
|
+
else:
|
|
226
|
+
f, obj = get_method_vars(callback)
|
|
227
|
+
wrkey = (f, id(obj))
|
|
228
|
+
self[wrkey] = obj
|
|
206
229
|
self.event_loop_map[wrkey] = loop
|
|
207
230
|
def iter_instances(self):
|
|
208
231
|
"""Iterate over the stored objects
|
|
@@ -221,8 +244,11 @@ class AioWeakMethodContainer(WeakMethodContainer):
|
|
|
221
244
|
"""
|
|
222
245
|
for wrkey, obj in self.iter_instances():
|
|
223
246
|
f, obj_id = wrkey
|
|
247
|
+
if f == 'function':
|
|
248
|
+
m = self[wrkey]
|
|
249
|
+
else:
|
|
250
|
+
m = getattr(obj, f.__name__)
|
|
224
251
|
loop = self.event_loop_map[wrkey]
|
|
225
|
-
m = getattr(obj, f.__name__)
|
|
226
252
|
yield loop, m
|
|
227
253
|
def _on_weakref_fin(self, key):
|
|
228
254
|
if key in self.event_loop_map:
|
pydispatch/decorators.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import Union, Iterable, Iterator, Callable
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
import pydispatch
|
|
5
|
+
from .dispatch import DoesNotExistError, _GLOBAL_DISPATCHER
|
|
6
|
+
from .utils import iscoroutinefunction, WeakMethodContainer
|
|
7
|
+
|
|
8
|
+
__all__ = ('receiver',)
|
|
9
|
+
|
|
10
|
+
class CallbackCache:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.cache = {}
|
|
13
|
+
|
|
14
|
+
def add(self, name: str, func: Callable):
|
|
15
|
+
wr_contain = self.cache.get(name)
|
|
16
|
+
if wr_contain is None:
|
|
17
|
+
wr_contain = WeakMethodContainer()
|
|
18
|
+
self.cache[name] = wr_contain
|
|
19
|
+
wr_contain.add_method(func)
|
|
20
|
+
|
|
21
|
+
def get(self, name: str) -> Iterator[Callable]:
|
|
22
|
+
wr_contain = self.cache.get(name)
|
|
23
|
+
if wr_contain is not None:
|
|
24
|
+
del self.cache[name]
|
|
25
|
+
yield from wr_contain.iter_methods()
|
|
26
|
+
yield from []
|
|
27
|
+
|
|
28
|
+
def __contains__(self, name: str) -> bool:
|
|
29
|
+
return name in self.cache
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_CACHED_CALLBACKS = CallbackCache()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def receiver(
|
|
36
|
+
event_name: Union[str, Iterable[str]],
|
|
37
|
+
cache: bool = False,
|
|
38
|
+
auto_register: bool = False
|
|
39
|
+
):
|
|
40
|
+
"""Decorator to bind a function or method to the :ref:`global-dispatcher`
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
|
|
44
|
+
>>> import pydispatch
|
|
45
|
+
|
|
46
|
+
>>> pydispatch.register_event('foo')
|
|
47
|
+
|
|
48
|
+
>>> @pydispatch.receiver('foo')
|
|
49
|
+
... def on_foo(value, **kwargs):
|
|
50
|
+
... print(f'on_foo: "{value}"')
|
|
51
|
+
|
|
52
|
+
>>> pydispatch.emit('foo', 'spam')
|
|
53
|
+
on_foo: "spam"
|
|
54
|
+
|
|
55
|
+
Using the *cache* argument
|
|
56
|
+
|
|
57
|
+
>>> @pydispatch.receiver('bar', cache=True)
|
|
58
|
+
... def on_bar(value, **kwargs):
|
|
59
|
+
... print(f'on_bar: "{value}"')
|
|
60
|
+
|
|
61
|
+
>>> pydispatch.register_event('bar')
|
|
62
|
+
>>> pydispatch.emit('bar', 'eggs')
|
|
63
|
+
on_bar: "eggs"
|
|
64
|
+
|
|
65
|
+
Using *auto_register*
|
|
66
|
+
|
|
67
|
+
>>> @pydispatch.receiver('baz', auto_register=True)
|
|
68
|
+
... def on_baz(value, **kwargs):
|
|
69
|
+
... print(f'on_baz: "{value}"')
|
|
70
|
+
|
|
71
|
+
>>> pydispatch.emit('baz', 'ham')
|
|
72
|
+
on_baz: "ham"
|
|
73
|
+
|
|
74
|
+
Receiving multiple events
|
|
75
|
+
|
|
76
|
+
>>> @pydispatch.receiver(['event_one', 'event_two'], auto_register=True)
|
|
77
|
+
... def on_event_one_or_two(value, **kwargs):
|
|
78
|
+
... print(value)
|
|
79
|
+
|
|
80
|
+
>>> pydispatch.emit('event_one', 1)
|
|
81
|
+
1
|
|
82
|
+
>>> pydispatch.emit('event_two', 2)
|
|
83
|
+
2
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
Arguments:
|
|
87
|
+
event_name: Event name (or names) to bind the callback to.
|
|
88
|
+
Can be a string or an :term:`iterable` of strings
|
|
89
|
+
cache: If the event does not exist yet and this is ``True``,
|
|
90
|
+
the callback will be held in a cache until it has been registered.
|
|
91
|
+
If ``False``, a :class:`~.DoesNotExistError` will be raised.
|
|
92
|
+
(Default is ``False``)
|
|
93
|
+
auto_register: If the event does not exist and this is ``True``,
|
|
94
|
+
it will be registered on the :ref:`global-dispatcher`.
|
|
95
|
+
(Default is ``False``)
|
|
96
|
+
|
|
97
|
+
.. versionadded:: 0.2.2
|
|
98
|
+
"""
|
|
99
|
+
def _decorator(func: Callable):
|
|
100
|
+
is_async = iscoroutinefunction(func)
|
|
101
|
+
|
|
102
|
+
if isinstance(event_name, str):
|
|
103
|
+
event_names = [event_name]
|
|
104
|
+
else:
|
|
105
|
+
event_names = event_name
|
|
106
|
+
bind_kwargs = {ev:func for ev in event_names}
|
|
107
|
+
if auto_register or cache:
|
|
108
|
+
for name in event_names:
|
|
109
|
+
if not _GLOBAL_DISPATCHER._has_event(name):
|
|
110
|
+
if auto_register:
|
|
111
|
+
pydispatch.register_event(name)
|
|
112
|
+
elif cache:
|
|
113
|
+
_CACHED_CALLBACKS.add(name, func)
|
|
114
|
+
del bind_kwargs[name]
|
|
115
|
+
if len(bind_kwargs):
|
|
116
|
+
if is_async:
|
|
117
|
+
bind_kwargs['__aio_loop__'] = asyncio.get_event_loop()
|
|
118
|
+
pydispatch.bind(**bind_kwargs)
|
|
119
|
+
return func
|
|
120
|
+
return _decorator
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _post_register_hook(*names):
|
|
124
|
+
"""Called in :func:`pydispatch.register_event` to search for cached callbacks
|
|
125
|
+
"""
|
|
126
|
+
for name in names:
|
|
127
|
+
if name not in _CACHED_CALLBACKS:
|
|
128
|
+
continue
|
|
129
|
+
bind_kwargs = {name:cb for cb in _CACHED_CALLBACKS.get(name)}
|
|
130
|
+
is_async = any((iscoroutinefunction(cb) for cb in bind_kwargs.values()))
|
|
131
|
+
if is_async:
|
|
132
|
+
loop = asyncio.get_event_loop()
|
|
133
|
+
pydispatch.bind_async(loop, **bind_kwargs)
|
|
134
|
+
else:
|
|
135
|
+
pydispatch.bind(**bind_kwargs)
|
pydispatch/dispatch.py
CHANGED
|
@@ -9,6 +9,51 @@ from pydispatch.properties import Property
|
|
|
9
9
|
import asyncio
|
|
10
10
|
from pydispatch.aioutils import AioWeakMethodContainer, AioEventWaiters
|
|
11
11
|
|
|
12
|
+
__all__ = (
|
|
13
|
+
'DoesNotExistError', 'ExistsError', 'EventExistsError',
|
|
14
|
+
'PropertyExistsError', 'Event', 'Dispatcher',
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DoesNotExistError(KeyError):
|
|
19
|
+
"""Raised when binding to an :class:`Event` or :class:`~.properties.Property`
|
|
20
|
+
that does not exist
|
|
21
|
+
|
|
22
|
+
.. versionadded:: 0.2.2
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, name):
|
|
25
|
+
self.name = name
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
return f'Event "{self.name}" not registered'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ExistsError(RuntimeError):
|
|
32
|
+
"""Raised when registering an event name that already exists
|
|
33
|
+
as either a normal :class:`Event` or :class:`~.properies.Property`
|
|
34
|
+
|
|
35
|
+
.. versionadded:: 0.2.2
|
|
36
|
+
"""
|
|
37
|
+
def __init__(self, name):
|
|
38
|
+
self.name = name
|
|
39
|
+
|
|
40
|
+
def __str__(self):
|
|
41
|
+
return f'"{self.name}" already exists'
|
|
42
|
+
|
|
43
|
+
class EventExistsError(ExistsError):
|
|
44
|
+
"""Raised when registering an event name that already exists
|
|
45
|
+
as an :class:`Event`
|
|
46
|
+
|
|
47
|
+
.. versionadded:: 0.2.2
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class PropertyExistsError(ExistsError):
|
|
52
|
+
"""Raised when registering an event name that already exists
|
|
53
|
+
as a :class:`~.properties.Property`
|
|
54
|
+
|
|
55
|
+
.. versionadded:: 0.2.2
|
|
56
|
+
"""
|
|
12
57
|
|
|
13
58
|
|
|
14
59
|
class Event(object):
|
|
@@ -108,10 +153,20 @@ class Dispatcher(object):
|
|
|
108
153
|
|
|
109
154
|
Args:
|
|
110
155
|
*names (str): Name or names of the events to register
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
EventExistsError: If an event with the given name already exists
|
|
159
|
+
PropertyExistsError: If a property with the given name already exists
|
|
160
|
+
|
|
161
|
+
.. versionchanged:: 0.2.2
|
|
162
|
+
:class:`ExistsError` exceptions are raised when attempting to
|
|
163
|
+
register an event or property that already exists
|
|
111
164
|
"""
|
|
112
165
|
for name in names:
|
|
113
166
|
if name in self.__events:
|
|
114
|
-
|
|
167
|
+
raise EventExistsError(name)
|
|
168
|
+
elif name in self.__property_events:
|
|
169
|
+
raise PropertyExistsError(name)
|
|
115
170
|
self.__events[name] = Event(name)
|
|
116
171
|
def bind(self, **kwargs):
|
|
117
172
|
"""Subscribes to events or to :class:`~pydispatch.properties.Property` updates
|
|
@@ -140,6 +195,8 @@ class Dispatcher(object):
|
|
|
140
195
|
argument ``"__aio_loop__"`` (an instance of
|
|
141
196
|
:class:`asyncio.BaseEventLoop`)
|
|
142
197
|
|
|
198
|
+
>>> import asyncio
|
|
199
|
+
|
|
143
200
|
>>> class Foo(Dispatcher):
|
|
144
201
|
... _events_ = ['test_event']
|
|
145
202
|
|
|
@@ -152,18 +209,28 @@ class Dispatcher(object):
|
|
|
152
209
|
... async def on_foo_test_event(self, *args, **kwargs):
|
|
153
210
|
... self.got_foo_event.set()
|
|
154
211
|
|
|
155
|
-
>>>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
>>> async def main():
|
|
213
|
+
... loop = asyncio.get_running_loop()
|
|
214
|
+
... foo = Foo()
|
|
215
|
+
... bar = Bar()
|
|
216
|
+
... foo.bind(test_event=bar.on_foo_test_event, __aio_loop__=loop)
|
|
217
|
+
... fut = asyncio.create_task(bar.wait_for_foo())
|
|
218
|
+
... foo.emit('test_event')
|
|
219
|
+
... await fut
|
|
160
220
|
|
|
161
|
-
>>>
|
|
162
|
-
>>> loop.run_until_complete(fut)
|
|
221
|
+
>>> asyncio.run(main())
|
|
163
222
|
got foo!
|
|
164
223
|
|
|
165
224
|
This can also be done using :meth:`bind_async`.
|
|
166
225
|
|
|
226
|
+
Raises:
|
|
227
|
+
DoesNotExistError: If attempting to bind to an event or
|
|
228
|
+
property that has not been registered
|
|
229
|
+
|
|
230
|
+
.. versionchanged:: 0.2.2
|
|
231
|
+
:class:`DoesNotExistError` is now raised when binding to
|
|
232
|
+
non-existent events or properties
|
|
233
|
+
|
|
167
234
|
.. versionadded:: 0.1.0
|
|
168
235
|
|
|
169
236
|
"""
|
|
@@ -174,7 +241,10 @@ class Dispatcher(object):
|
|
|
174
241
|
if name in props:
|
|
175
242
|
e = props[name]
|
|
176
243
|
else:
|
|
177
|
-
|
|
244
|
+
try:
|
|
245
|
+
e = events[name]
|
|
246
|
+
except KeyError:
|
|
247
|
+
raise DoesNotExistError(name)
|
|
178
248
|
e.add_listener(cb, __aio_loop__=aio_loop)
|
|
179
249
|
def unbind(self, *args):
|
|
180
250
|
"""Unsubscribes from events or :class:`~pydispatch.properties.Property` updates
|
|
@@ -224,10 +294,21 @@ class Dispatcher(object):
|
|
|
224
294
|
name (str): The name of the :class:`Event` to dispatch
|
|
225
295
|
*args (Optional): Positional arguments to be sent to listeners
|
|
226
296
|
**kwargs (Optional): Keyword arguments to be sent to listeners
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
DoesNotExistError: If attempting to emit an event or
|
|
300
|
+
property that has not been registered
|
|
301
|
+
|
|
302
|
+
.. versionchanged:: 0.2.2
|
|
303
|
+
:class:`DoesNotExistError` is now raised if the event or property
|
|
304
|
+
does not exist
|
|
227
305
|
"""
|
|
228
306
|
e = self.__property_events.get(name)
|
|
229
307
|
if e is None:
|
|
230
|
-
|
|
308
|
+
try:
|
|
309
|
+
e = self.__events[name]
|
|
310
|
+
except KeyError:
|
|
311
|
+
raise DoesNotExistError(name)
|
|
231
312
|
return e(*args, **kwargs)
|
|
232
313
|
def get_dispatcher_event(self, name):
|
|
233
314
|
"""Retrieves an Event object by name
|
|
@@ -239,11 +320,21 @@ class Dispatcher(object):
|
|
|
239
320
|
Returns:
|
|
240
321
|
The :class:`Event` instance for the event or property definition
|
|
241
322
|
|
|
323
|
+
Raises:
|
|
324
|
+
DoesNotExistError: If no event or property with the given name exists
|
|
325
|
+
|
|
326
|
+
.. versionchanged:: 0.2.2
|
|
327
|
+
:class:`DoesNotExistError` is now raised if the event or property
|
|
328
|
+
does not exist
|
|
329
|
+
|
|
242
330
|
.. versionadded:: 0.1.0
|
|
243
331
|
"""
|
|
244
332
|
e = self.__property_events.get(name)
|
|
245
333
|
if e is None:
|
|
246
|
-
|
|
334
|
+
try:
|
|
335
|
+
e = self.__events[name]
|
|
336
|
+
except KeyError:
|
|
337
|
+
raise DoesNotExistError(name)
|
|
247
338
|
return e
|
|
248
339
|
def emission_lock(self, name):
|
|
249
340
|
"""Holds emission of events and dispatches the last event on release
|
|
@@ -279,9 +370,26 @@ class Dispatcher(object):
|
|
|
279
370
|
The context manager is re-entrant, meaning that multiple calls to
|
|
280
371
|
this method within nested context scopes are possible.
|
|
281
372
|
|
|
373
|
+
Raises:
|
|
374
|
+
DoesNotExistError: If no event or property with the given name exists
|
|
375
|
+
|
|
376
|
+
.. versionchanged:: 0.2.2
|
|
377
|
+
:class:`DoesNotExistError` is now raised if the event or property
|
|
378
|
+
does not exist
|
|
379
|
+
|
|
282
380
|
.. _PEP 492: https://www.python.org/dev/peps/pep-0492/#asynchronous-context-managers-and-async-with
|
|
283
381
|
"""
|
|
284
|
-
e = self.
|
|
285
|
-
if e is None:
|
|
286
|
-
e = self.__events[name]
|
|
382
|
+
e = self.get_dispatcher_event(name)
|
|
287
383
|
return e.emission_lock
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class _GlobalDispatcher(Dispatcher):
|
|
387
|
+
def _has_event(self, name):
|
|
388
|
+
try:
|
|
389
|
+
self.get_dispatcher_event(name)
|
|
390
|
+
except KeyError:
|
|
391
|
+
return False
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
_GLOBAL_DISPATCHER = _GlobalDispatcher()
|
pydispatch/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import weakref
|
|
2
|
-
from weakref import ref
|
|
3
|
-
from _weakref import
|
|
2
|
+
from weakref import ref
|
|
3
|
+
from _weakref import _remove_dead_weakref # type: ignore[import]
|
|
4
4
|
import types
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
@@ -9,6 +9,9 @@ def get_method_vars(m):
|
|
|
9
9
|
obj = m.__self__
|
|
10
10
|
return f, obj
|
|
11
11
|
|
|
12
|
+
def isfunction(m):
|
|
13
|
+
return isinstance(m, types.FunctionType)
|
|
14
|
+
|
|
12
15
|
def iscoroutinefunction(obj):
|
|
13
16
|
return asyncio.iscoroutinefunction(obj)
|
|
14
17
|
|
|
@@ -28,7 +31,7 @@ class WeakMethodContainer(weakref.WeakValueDictionary):
|
|
|
28
31
|
Args:
|
|
29
32
|
m: The instance method or function to store
|
|
30
33
|
"""
|
|
31
|
-
if
|
|
34
|
+
if isfunction(m):
|
|
32
35
|
self['function', id(m)] = m
|
|
33
36
|
else:
|
|
34
37
|
f, obj = get_method_vars(m)
|
|
@@ -40,7 +43,7 @@ class WeakMethodContainer(weakref.WeakValueDictionary):
|
|
|
40
43
|
Args:
|
|
41
44
|
m: The instance method or function to remove
|
|
42
45
|
"""
|
|
43
|
-
if
|
|
46
|
+
if isfunction(m):
|
|
44
47
|
wrkey = ('function', id(m))
|
|
45
48
|
else:
|
|
46
49
|
f, obj = get_method_vars(m)
|
|
@@ -111,6 +114,12 @@ class InformativeWVDict(weakref.WeakValueDictionary):
|
|
|
111
114
|
_remove_dead_weakref(self.data, wr.key)
|
|
112
115
|
self._data_del_callback(wr.key)
|
|
113
116
|
self._remove = remove
|
|
117
|
+
|
|
118
|
+
# `_pending_removals` and `_iterating` were removed in Python 3.14.
|
|
119
|
+
# To maintain compatibility with earlier versions, we reintroduce them here.
|
|
120
|
+
self._pending_removals = []
|
|
121
|
+
self._iterating = set()
|
|
122
|
+
|
|
114
123
|
self.data = InformativeDict()
|
|
115
124
|
self.data.del_callback = self._data_del_callback
|
|
116
125
|
def _data_del_callback(self, key):
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-dispatch
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Lightweight Event Handling
|
|
5
|
-
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Lightweight Event Handling Library for Python
|
|
5
|
+
Keywords: event,properties,dispatch
|
|
6
6
|
Author: Matthew Reid
|
|
7
|
-
Author-email: matt@nomadic-recording.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Project-URL: Documentation, https://python-dispatch.readthedocs.io
|
|
10
|
-
Project-URL: Source, https://github.com/nocarryr/python-dispatch
|
|
11
|
-
Project-URL: Tracker, https://github.com/nocarryr/python-dispatch/issues
|
|
12
|
-
Keywords: event properties dispatch
|
|
13
|
-
Platform: any
|
|
7
|
+
Author-email: Matthew Reid <matt@nomadic-recording.com>
|
|
8
|
+
License-Expression: MIT
|
|
14
9
|
Classifier: Development Status :: 3 - Alpha
|
|
15
10
|
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
17
11
|
Classifier: Operating System :: OS Independent
|
|
18
12
|
Classifier: Programming Language :: Python
|
|
19
13
|
Classifier: Topic :: Software Development
|
|
20
14
|
Classifier: Programming Language :: Python :: 3
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
23
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
24
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
25
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
26
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
27
22
|
Classifier: Topic :: Software Development :: Libraries
|
|
28
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Dist: importlib-metadata ; python_full_version < '3.8'
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Project-URL: Documentation, https://python-dispatch.readthedocs.io/
|
|
27
|
+
Project-URL: Download, https://pypi.org/project/python-dispatch/
|
|
28
|
+
Project-URL: Source, https://github.com/nocarryr/python-dispatch
|
|
29
|
+
Project-URL: Tracker, https://github.com/nocarryr/python-dispatch/issues
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE.txt
|
|
31
31
|
|
|
32
32
|
# python-dispatch
|
|
33
33
|
Lightweight event handling for Python
|
|
34
34
|
|
|
35
|
-
[](https://github.com/astral-sh/uv)[](https://coveralls.io/github/nocarryr/python-dispatch?branch=master)[](https://badge.fury.io/py/python-dispatch)[](https://raw.githubusercontent.com/nocarryr/python-dispatch/master/LICENSE.txt)
|
|
36
36
|
|
|
37
37
|
## Description
|
|
38
38
|
This is an implementation of the "Observer Pattern" with inspiration from the
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pydispatch/__init__.py,sha256=qt4QDOYq_QGq0bMZizAvnhhzLr7LkZe9PBHilTjc74Y,2190
|
|
2
|
+
pydispatch/aioutils.py,sha256=tv-ddi8tMwqtOJGNqONYb2Y-PKXyOJG4wsX3EzmgBsw,9848
|
|
3
|
+
pydispatch/decorators.py,sha256=FFts0T9wvTVAgC9KGZSkpn_Nneecl1gAzufgP6nI4iQ,4213
|
|
4
|
+
pydispatch/dispatch.py,sha256=Jbb7bl7kdsXlfQsCM8Jehui7qpSH1GhawEgPLk-lyAo,13508
|
|
5
|
+
pydispatch/properties.py,sha256=Pt4J32bqz3xT5BwmIO3Idl8vmkNbei2R0g_Vqwi7RH0,13138
|
|
6
|
+
pydispatch/utils.py,sha256=ubuGYHi0rxNS_Dbhqftfyr6yANS0OlaB6Ni9_x_kEP4,6535
|
|
7
|
+
python_dispatch-0.2.3.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
|
|
8
|
+
python_dispatch-0.2.3.dist-info/METADATA,sha256=S1e1HF_lMN-gVv80kfCMUgbLVTNtLNslwkaW1A6NMsA,5802
|
|
9
|
+
python_dispatch-0.2.3.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2016 Matthew Reid
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
pydispatch/__init__.py,sha256=Lqfmw_j-YoJOJyRoSHc4VnPUoDF5PFqTCvSlR0RFs9I,615
|
|
2
|
-
pydispatch/aioutils.py,sha256=M4n_H0NsuW0NpnCtU2y7FVWXtXA_JqQSMTWGJgELVY0,9027
|
|
3
|
-
pydispatch/dispatch.py,sha256=xJvz6-0kEbgoh7LJJKqwZHc-yUrTYIEBwpxw8AC79G4,10265
|
|
4
|
-
pydispatch/properties.py,sha256=Pt4J32bqz3xT5BwmIO3Idl8vmkNbei2R0g_Vqwi7RH0,13138
|
|
5
|
-
pydispatch/utils.py,sha256=VYM6dNcBBGKpREOtmEZwO-xfJr41dsuyIfBKv8HtrBw,6292
|
|
6
|
-
python_dispatch-0.2.1.dist-info/LICENSE.txt,sha256=muuSBeZ0K6AotNoWadQaHzGO9CtXBFBemAsXRL170tA,1069
|
|
7
|
-
python_dispatch-0.2.1.dist-info/METADATA,sha256=63HpIqPlkRW5NPdWmsdKJBnfeNCayjPdSi2hjaeXOHo,5558
|
|
8
|
-
python_dispatch-0.2.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
9
|
-
python_dispatch-0.2.1.dist-info/top_level.txt,sha256=BW69gM2IyIPfFk8J13eRwxzVlic046roE3WH5bya5BI,11
|
|
10
|
-
python_dispatch-0.2.1.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pydispatch
|