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 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 Dispatcher, Event
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 _weakref import ref
4
- from _weakrefset import _IterationGuard
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
- f, obj = get_method_vars(callback)
204
- wrkey = (f, id(obj))
205
- self[wrkey] = obj
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:
@@ -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
- continue
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
- >>> loop = asyncio.get_event_loop()
156
- >>> foo = Foo()
157
- >>> bar = Bar()
158
- >>> foo.bind(test_event=bar.on_foo_test_event, __aio_loop__=loop)
159
- >>> fut = asyncio.ensure_future(bar.wait_for_foo())
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
- >>> foo.emit('test_event')
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
- e = events[name]
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
- e = self.__events[name]
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
- e = self.__events[name]
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.__property_events.get(name)
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, _remove_dead_weakref
3
- from _weakref import ref
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 isinstance(m, types.FunctionType):
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 isinstance(m, types.FunctionType) and not iscoroutinefunction(m):
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
1
+ Metadata-Version: 2.4
2
2
  Name: python-dispatch
3
- Version: 0.2.1
4
- Summary: Lightweight Event Handling
5
- Home-page: https://github.com/nocarryr/python-dispatch
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
- [![Build Status](https://travis-ci.org/nocarryr/python-dispatch.svg?branch=master)](https://travis-ci.org/nocarryr/python-dispatch)[![Coverage Status](https://coveralls.io/repos/github/nocarryr/python-dispatch/badge.svg?branch=master)](https://coveralls.io/github/nocarryr/python-dispatch?branch=master)[![PyPI version](https://badge.fury.io/py/python-dispatch.svg)](https://badge.fury.io/py/python-dispatch)[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nocarryr/python-dispatch/master/LICENSE.txt)
35
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nocarryr/python-dispatch/ci.yml)[![Coverage Status](https://coveralls.io/repos/github/nocarryr/python-dispatch/badge.svg?branch=master)](https://coveralls.io/github/nocarryr/python-dispatch?branch=master)[![PyPI version](https://badge.fury.io/py/python-dispatch.svg)](https://badge.fury.io/py/python-dispatch)[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.18
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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,5 +0,0 @@
1
- Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.38.4)
3
- Root-Is-Purelib: true
4
- Tag: py3-none-any
5
-
@@ -1 +0,0 @@
1
- pydispatch