aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/blinker
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-05-13 00:00:59 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-05-13 00:06:28 +0300
commitd6b290c39d078c44e88669fcde8da65fb4d2a03f (patch)
tree27b34ae7cbc09cec622689aea9222252cecc18cf /contrib/python/blinker
parentd502666ab38ad7f8b15bc6e839fd00a4d07cd274 (diff)
downloadydb-d6b290c39d078c44e88669fcde8da65fb4d2a03f.tar.gz
Intermediate changes
Diffstat (limited to 'contrib/python/blinker')
-rw-r--r--contrib/python/blinker/py3/.dist-info/METADATA84
-rw-r--r--contrib/python/blinker/py3/LICENSE.txt (renamed from contrib/python/blinker/py3/LICENSE.rst)0
-rw-r--r--contrib/python/blinker/py3/README.md43
-rw-r--r--contrib/python/blinker/py3/README.rst40
-rw-r--r--contrib/python/blinker/py3/blinker/__init__.py61
-rw-r--r--contrib/python/blinker/py3/blinker/_utilities.py107
-rw-r--r--contrib/python/blinker/py3/blinker/base.py365
-rw-r--r--contrib/python/blinker/py3/ya.make2
8 files changed, 362 insertions, 340 deletions
diff --git a/contrib/python/blinker/py3/.dist-info/METADATA b/contrib/python/blinker/py3/.dist-info/METADATA
index f96613c4b8..2eb62efbef 100644
--- a/contrib/python/blinker/py3/.dist-info/METADATA
+++ b/contrib/python/blinker/py3/.dist-info/METADATA
@@ -1,62 +1,60 @@
Metadata-Version: 2.1
Name: blinker
-Version: 1.7.0
+Version: 1.8.0
Summary: Fast, simple object-to-object and broadcast signaling
-Keywords: signal,emit,events,broadcast
-Author-email: Jason Kirtland <jek@discorporate.us>
+Author: Jason Kirtland
Maintainer-email: Pallets Ecosystem <contact@palletsprojects.com>
Requires-Python: >=3.8
-Description-Content-Type: text/x-rst
+Description-Content-Type: text/markdown
Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
-Classifier: Topic :: Software Development :: Libraries
+Classifier: Typing :: Typed
Project-URL: Chat, https://discord.gg/pallets
Project-URL: Documentation, https://blinker.readthedocs.io
-Project-URL: Homepage, https://blinker.readthedocs.io
-Project-URL: Issue Tracker, https://github.com/pallets-eco/blinker/issues/
-Project-URL: Source Code, https://github.com/pallets-eco/blinker/
+Project-URL: Source, https://github.com/pallets-eco/blinker/
-Blinker
-=======
+# Blinker
Blinker provides a fast dispatching system that allows any number of
interested parties to subscribe to events, or "signals".
+
+## Pallets Community Ecosystem
+
+> [!IMPORTANT]\
+> This project is part of the Pallets Community Ecosystem. Pallets is the open
+> source organization that maintains Flask; Pallets-Eco enables community
+> maintenance of related projects. If you are interested in helping maintain
+> this project, please reach out on [the Pallets Discord server][discord].
+>
+> [discord]: https://discord.gg/pallets
+
+
+## Example
+
Signal receivers can subscribe to specific senders or receive signals
sent by any sender.
-.. code-block:: pycon
-
- >>> from blinker import signal
- >>> started = signal('round-started')
- >>> def each(round):
- ... print(f"Round {round}")
- ...
- >>> started.connect(each)
-
- >>> def round_two(round):
- ... print("This is round two.")
- ...
- >>> started.connect(round_two, sender=2)
-
- >>> for round in range(1, 4):
- ... started.send(round)
- ...
- Round 1!
- Round 2!
- This is round two.
- Round 3!
-
-
-Links
------
-
-- Documentation: https://blinker.readthedocs.io/
-- Changes: https://blinker.readthedocs.io/#changes
-- PyPI Releases: https://pypi.org/project/blinker/
-- Source Code: https://github.com/pallets-eco/blinker/
-- Issue Tracker: https://github.com/pallets-eco/blinker/issues/
+```pycon
+>>> from blinker import signal
+>>> started = signal('round-started')
+>>> def each(round):
+... print(f"Round {round}")
+...
+>>> started.connect(each)
+
+>>> def round_two(round):
+... print("This is round two.")
+...
+>>> started.connect(round_two, sender=2)
+
+>>> for round in range(1, 4):
+... started.send(round)
+...
+Round 1!
+Round 2!
+This is round two.
+Round 3!
+```
diff --git a/contrib/python/blinker/py3/LICENSE.rst b/contrib/python/blinker/py3/LICENSE.txt
index 79c9825adb..79c9825adb 100644
--- a/contrib/python/blinker/py3/LICENSE.rst
+++ b/contrib/python/blinker/py3/LICENSE.txt
diff --git a/contrib/python/blinker/py3/README.md b/contrib/python/blinker/py3/README.md
new file mode 100644
index 0000000000..a3969bbbe0
--- /dev/null
+++ b/contrib/python/blinker/py3/README.md
@@ -0,0 +1,43 @@
+# Blinker
+
+Blinker provides a fast dispatching system that allows any number of
+interested parties to subscribe to events, or "signals".
+
+
+## Pallets Community Ecosystem
+
+> [!IMPORTANT]\
+> This project is part of the Pallets Community Ecosystem. Pallets is the open
+> source organization that maintains Flask; Pallets-Eco enables community
+> maintenance of related projects. If you are interested in helping maintain
+> this project, please reach out on [the Pallets Discord server][discord].
+>
+> [discord]: https://discord.gg/pallets
+
+
+## Example
+
+Signal receivers can subscribe to specific senders or receive signals
+sent by any sender.
+
+```pycon
+>>> from blinker import signal
+>>> started = signal('round-started')
+>>> def each(round):
+... print(f"Round {round}")
+...
+>>> started.connect(each)
+
+>>> def round_two(round):
+... print("This is round two.")
+...
+>>> started.connect(round_two, sender=2)
+
+>>> for round in range(1, 4):
+... started.send(round)
+...
+Round 1!
+Round 2!
+This is round two.
+Round 3!
+```
diff --git a/contrib/python/blinker/py3/README.rst b/contrib/python/blinker/py3/README.rst
deleted file mode 100644
index e3bc6d4781..0000000000
--- a/contrib/python/blinker/py3/README.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-Blinker
-=======
-
-Blinker provides a fast dispatching system that allows any number of
-interested parties to subscribe to events, or "signals".
-
-Signal receivers can subscribe to specific senders or receive signals
-sent by any sender.
-
-.. code-block:: pycon
-
- >>> from blinker import signal
- >>> started = signal('round-started')
- >>> def each(round):
- ... print(f"Round {round}")
- ...
- >>> started.connect(each)
-
- >>> def round_two(round):
- ... print("This is round two.")
- ...
- >>> started.connect(round_two, sender=2)
-
- >>> for round in range(1, 4):
- ... started.send(round)
- ...
- Round 1!
- Round 2!
- This is round two.
- Round 3!
-
-
-Links
------
-
-- Documentation: https://blinker.readthedocs.io/
-- Changes: https://blinker.readthedocs.io/#changes
-- PyPI Releases: https://pypi.org/project/blinker/
-- Source Code: https://github.com/pallets-eco/blinker/
-- Issue Tracker: https://github.com/pallets-eco/blinker/issues/
diff --git a/contrib/python/blinker/py3/blinker/__init__.py b/contrib/python/blinker/py3/blinker/__init__.py
index d014caa0ff..c93527eca8 100644
--- a/contrib/python/blinker/py3/blinker/__init__.py
+++ b/contrib/python/blinker/py3/blinker/__init__.py
@@ -1,19 +1,60 @@
-from blinker.base import ANY
-from blinker.base import NamedSignal
-from blinker.base import Namespace
-from blinker.base import receiver_connected
-from blinker.base import Signal
-from blinker.base import signal
-from blinker.base import WeakNamespace
+from __future__ import annotations
+
+import typing as t
+
+from .base import ANY
+from .base import default_namespace
+from .base import NamedSignal
+from .base import Namespace
+from .base import Signal
+from .base import signal
__all__ = [
"ANY",
+ "default_namespace",
"NamedSignal",
"Namespace",
"Signal",
- "WeakNamespace",
- "receiver_connected",
"signal",
]
-__version__ = "1.7.0"
+
+def __getattr__(name: str) -> t.Any:
+ import warnings
+
+ if name == "__version__":
+ import importlib.metadata
+
+ warnings.warn(
+ "The '__version__' attribute is deprecated and will be removed in"
+ " Blinker 1.9.0. Use feature detection or"
+ " 'importlib.metadata.version(\"blinker\")' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return importlib.metadata.version("blinker")
+
+ if name == "receiver_connected":
+ from .base import _receiver_connected
+
+ warnings.warn(
+ "The global 'receiver_connected' signal is deprecated and will be"
+ " removed in Blinker 1.9. Use 'Signal.receiver_connected' and"
+ " 'Signal.receiver_disconnected' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _receiver_connected
+
+ if name == "WeakNamespace":
+ from .base import _WeakNamespace
+
+ warnings.warn(
+ "'WeakNamespace' is deprecated and will be removed in Blinker 1.9."
+ " Use 'Namespace' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _WeakNamespace
+
+ raise AttributeError(name)
diff --git a/contrib/python/blinker/py3/blinker/_utilities.py b/contrib/python/blinker/py3/blinker/_utilities.py
index 4b711c67d3..784ba4e121 100644
--- a/contrib/python/blinker/py3/blinker/_utilities.py
+++ b/contrib/python/blinker/py3/blinker/_utilities.py
@@ -1,105 +1,52 @@
from __future__ import annotations
+import inspect
import typing as t
from weakref import ref
+from weakref import WeakMethod
-from blinker._saferef import BoundMethodWeakref
+T = t.TypeVar("T")
-IdentityType = t.Union[t.Tuple[int, int], str, int]
+class Symbol:
+ """A constant symbol, nicer than ``object()``. Repeated calls return the
+ same instance.
-class _symbol:
- def __init__(self, name):
- """Construct a new named symbol."""
- self.__name__ = self.name = name
-
- def __reduce__(self):
- return symbol, (self.name,)
-
- def __repr__(self):
- return self.name
-
-
-_symbol.__name__ = "symbol"
-
-
-class symbol:
- """A constant symbol.
-
- >>> symbol('foo') is symbol('foo')
+ >>> Symbol('foo') is Symbol('foo')
True
- >>> symbol('foo')
+ >>> Symbol('foo')
foo
-
- A slight refinement of the MAGICCOOKIE=object() pattern. The primary
- advantage of symbol() is its repr(). They are also singletons.
-
- Repeated calls of symbol('name') will all return the same instance.
-
"""
- symbols = {} # type: ignore[var-annotated]
+ symbols: t.ClassVar[dict[str, Symbol]] = {}
- def __new__(cls, name):
- try:
+ def __new__(cls, name: str) -> Symbol:
+ if name in cls.symbols:
return cls.symbols[name]
- except KeyError:
- return cls.symbols.setdefault(name, _symbol(name))
-
-def hashable_identity(obj: object) -> IdentityType:
- if hasattr(obj, "__func__"):
- return (id(obj.__func__), id(obj.__self__)) # type: ignore[attr-defined]
- elif hasattr(obj, "im_func"):
- return (id(obj.im_func), id(obj.im_self)) # type: ignore[attr-defined]
- elif isinstance(obj, (int, str)):
+ obj = super().__new__(cls)
+ cls.symbols[name] = obj
return obj
- else:
- return id(obj)
-
-WeakTypes = (ref, BoundMethodWeakref)
-
-
-class annotatable_weakref(ref):
- """A weakref.ref that supports custom instance attributes."""
-
- receiver_id: t.Optional[IdentityType]
- sender_id: t.Optional[IdentityType]
+ def __init__(self, name: str) -> None:
+ self.name = name
+ def __repr__(self) -> str:
+ return self.name
-def reference( # type: ignore[no-untyped-def]
- object, callback=None, **annotations
-) -> annotatable_weakref:
- """Return an annotated weak ref."""
- if callable(object):
- weak = callable_reference(object, callback)
- else:
- weak = annotatable_weakref(object, callback)
- for key, value in annotations.items():
- setattr(weak, key, value)
- return weak # type: ignore[no-any-return]
+ def __getnewargs__(self) -> tuple[t.Any]:
+ return (self.name,)
-def callable_reference(object, callback=None):
- """Return an annotated weak ref, supporting bound instance methods."""
- if hasattr(object, "im_self") and object.im_self is not None:
- return BoundMethodWeakref(target=object, on_delete=callback)
- elif hasattr(object, "__self__") and object.__self__ is not None:
- return BoundMethodWeakref(target=object, on_delete=callback)
- return annotatable_weakref(object, callback)
+def make_id(obj: object) -> t.Hashable:
+ if inspect.ismethod(obj):
+ return id(obj.__func__), id(obj.__self__)
+ return id(obj)
-class lazy_property:
- """A @property that is only evaluated once."""
- def __init__(self, deferred):
- self._deferred = deferred
- self.__doc__ = deferred.__doc__
+def make_ref(obj: T, callback: t.Callable[[ref[T]], None] | None = None) -> ref[T]:
+ if inspect.ismethod(obj):
+ return WeakMethod(obj, callback) # type: ignore[arg-type, return-value]
- def __get__(self, obj, cls):
- if obj is None:
- return self
- value = self._deferred(obj)
- setattr(obj, self._deferred.__name__, value)
- return value
+ return ref(obj, callback)
diff --git a/contrib/python/blinker/py3/blinker/base.py b/contrib/python/blinker/py3/blinker/base.py
index b9d703586d..8aa65255b9 100644
--- a/contrib/python/blinker/py3/blinker/base.py
+++ b/contrib/python/blinker/py3/blinker/base.py
@@ -5,43 +5,40 @@ API client code seen in a blog post. Signals are first-class objects and
each manages its own receivers and message emission.
The :func:`signal` function provides singleton behavior for named signals.
-
"""
+
from __future__ import annotations
import typing as t
+import warnings
+import weakref
from collections import defaultdict
from contextlib import contextmanager
+from functools import cached_property
from inspect import iscoroutinefunction
-from warnings import warn
from weakref import WeakValueDictionary
-from blinker._utilities import annotatable_weakref
-from blinker._utilities import hashable_identity
-from blinker._utilities import IdentityType
-from blinker._utilities import lazy_property
-from blinker._utilities import reference
-from blinker._utilities import symbol
-from blinker._utilities import WeakTypes
+from ._utilities import make_id
+from ._utilities import make_ref
+from ._utilities import Symbol
if t.TYPE_CHECKING:
import typing_extensions as te
- T_callable = t.TypeVar("T_callable", bound=t.Callable[..., t.Any])
-
+ F = t.TypeVar("F", bound=t.Callable[..., t.Any])
T = t.TypeVar("T")
P = te.ParamSpec("P")
- AsyncWrapperType = t.Callable[[t.Callable[P, t.Awaitable[T]]], t.Callable[P, T]]
- SyncWrapperType = t.Callable[[t.Callable[P, T]], t.Callable[P, t.Awaitable[T]]]
+ class PAsyncWrapper(t.Protocol):
+ def __call__(self, f: t.Callable[P, t.Awaitable[T]]) -> t.Callable[P, T]: ...
+
+ class PSyncWrapper(t.Protocol):
+ def __call__(self, f: t.Callable[P, T]) -> t.Callable[P, t.Awaitable[T]]: ...
-ANY = symbol("ANY")
-ANY.__doc__ = 'Token for "any sender".'
-ANY_ID = 0
-# NOTE: We need a reference to cast for use in weakref callbacks otherwise
-# t.cast may have already been set to None during finalization.
-cast = t.cast
+ANY = Symbol("ANY")
+"""Token for "any sender"."""
+ANY_ID = 0
class Signal:
@@ -51,9 +48,9 @@ class Signal:
#: without an additional import.
ANY = ANY
- set_class: type[set] = set
+ set_class: type[set[t.Any]] = set
- @lazy_property
+ @cached_property
def receiver_connected(self) -> Signal:
"""Emitted after each :meth:`connect`.
@@ -61,11 +58,10 @@ class Signal:
arguments are passed through: *receiver*, *sender*, and *weak*.
.. versionadded:: 1.2
-
"""
return Signal(doc="Emitted after a receiver connects.")
- @lazy_property
+ @cached_property
def receiver_disconnected(self) -> Signal:
"""Emitted after :meth:`disconnect`.
@@ -85,37 +81,31 @@ class Signal:
callback on weak receivers and senders.
.. versionadded:: 1.2
-
"""
return Signal(doc="Emitted after a receiver disconnects.")
def __init__(self, doc: str | None = None) -> None:
"""
- :param doc: optional. If provided, will be assigned to the signal's
- __doc__ attribute.
-
+ :param doc: Set the instance's ``__doc__`` attribute for documentation.
"""
if doc:
self.__doc__ = doc
+
#: A mapping of connected receivers.
#:
#: The values of this mapping are not meaningful outside of the
#: internal :class:`Signal` implementation, however the boolean value
#: of the mapping is useful as an extremely efficient check to see if
#: any receivers are connected to the signal.
- self.receivers: dict[IdentityType, t.Callable | annotatable_weakref] = {}
- self.is_muted = False
- self._by_receiver: dict[IdentityType, set[IdentityType]] = defaultdict(
- self.set_class
- )
- self._by_sender: dict[IdentityType, set[IdentityType]] = defaultdict(
- self.set_class
- )
- self._weak_senders: dict[IdentityType, annotatable_weakref] = {}
-
- def connect(
- self, receiver: T_callable, sender: t.Any = ANY, weak: bool = True
- ) -> T_callable:
+ self.receivers: dict[
+ t.Any, weakref.ref[t.Callable[..., t.Any]] | t.Callable[..., t.Any]
+ ] = {}
+ self.is_muted: bool = False
+ self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
+ self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
+ self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {}
+
+ def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F:
"""Connect *receiver* to signal events sent by *sender*.
:param receiver: A callable. Will be invoked by :meth:`send` with
@@ -132,60 +122,51 @@ class Signal:
:param weak: If true, the Signal will hold a weakref to *receiver*
and automatically disconnect when *receiver* goes out of scope or
is garbage collected. Defaults to True.
-
"""
- receiver_id = hashable_identity(receiver)
- receiver_ref: T_callable | annotatable_weakref
+ receiver_id = make_id(receiver)
+ sender_id = ANY_ID if sender is ANY else make_id(sender)
if weak:
- receiver_ref = reference(receiver, self._cleanup_receiver)
- receiver_ref.receiver_id = receiver_id
- else:
- receiver_ref = receiver
- sender_id: IdentityType
- if sender is ANY:
- sender_id = ANY_ID
+ self.receivers[receiver_id] = make_ref(
+ receiver, self._make_cleanup_receiver(receiver_id)
+ )
else:
- sender_id = hashable_identity(sender)
+ self.receivers[receiver_id] = receiver
- self.receivers.setdefault(receiver_id, receiver_ref)
self._by_sender[sender_id].add(receiver_id)
self._by_receiver[receiver_id].add(sender_id)
- del receiver_ref
if sender is not ANY and sender_id not in self._weak_senders:
- # wire together a cleanup for weakref-able senders
+ # store a cleanup for weakref-able senders
try:
- sender_ref = reference(sender, self._cleanup_sender)
- sender_ref.sender_id = sender_id
+ self._weak_senders[sender_id] = make_ref(
+ sender, self._make_cleanup_sender(sender_id)
+ )
except TypeError:
pass
- else:
- self._weak_senders.setdefault(sender_id, sender_ref)
- del sender_ref
- # broadcast this connection. if receivers raise, disconnect.
if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers:
try:
self.receiver_connected.send(
self, receiver=receiver, sender=sender, weak=weak
)
- except TypeError as e:
+ except TypeError:
+ # TODO no explanation or test for this
self.disconnect(receiver, sender)
- raise e
- if receiver_connected.receivers and self is not receiver_connected:
+ raise
+
+ if _receiver_connected.receivers and self is not _receiver_connected:
try:
- receiver_connected.send(
+ _receiver_connected.send(
self, receiver_arg=receiver, sender_arg=sender, weak_arg=weak
)
- except TypeError as e:
+ except TypeError:
self.disconnect(receiver, sender)
- raise e
+ raise
+
return receiver
- def connect_via(
- self, sender: t.Any, weak: bool = False
- ) -> t.Callable[[T_callable], T_callable]:
+ def connect_via(self, sender: t.Any, weak: bool = False) -> t.Callable[[F], F]:
"""Connect the decorated function as a receiver for *sender*.
:param sender: Any object or :obj:`ANY`. The decorated function
@@ -204,10 +185,9 @@ class Signal:
.. versionadded:: 1.1
-
"""
- def decorator(fn: T_callable) -> T_callable:
+ def decorator(fn: F) -> F:
self.connect(fn, sender, weak)
return fn
@@ -215,7 +195,7 @@ class Signal:
@contextmanager
def connected_to(
- self, receiver: t.Callable, sender: t.Any = ANY
+ self, receiver: t.Callable[..., t.Any], sender: t.Any = ANY
) -> t.Generator[None, None, None]:
"""Execute a block with the signal temporarily connected to *receiver*.
@@ -237,6 +217,7 @@ class Signal:
"""
self.connect(receiver, sender=sender, weak=False)
+
try:
yield None
finally:
@@ -248,15 +229,14 @@ class Signal:
Useful for test purposes.
"""
self.is_muted = True
+
try:
yield None
- except Exception as e:
- raise e
finally:
self.is_muted = False
def temporarily_connected_to(
- self, receiver: t.Callable, sender: t.Any = ANY
+ self, receiver: t.Callable[..., t.Any], sender: t.Any = ANY
) -> t.ContextManager[None]:
"""An alias for :meth:`connected_to`.
@@ -265,23 +245,25 @@ class Signal:
.. versionadded:: 0.9
- .. versionchanged:: 1.1
- Renamed to :meth:`connected_to`. ``temporarily_connected_to`` was
- deprecated in 1.2 and will be removed in a subsequent version.
-
+ .. deprecated:: 1.1
+ Renamed to ``connected_to``. Will be removed in Blinker 1.9.
"""
- warn(
- "temporarily_connected_to is deprecated; use connected_to instead.",
+ warnings.warn(
+ "'temporarily_connected_to' is renamed to 'connected_to'. The old name is"
+ " deprecated and will be removed in Blinker 1.9.",
DeprecationWarning,
+ stacklevel=2,
)
return self.connected_to(receiver, sender)
def send(
self,
- *sender: t.Any,
- _async_wrapper: AsyncWrapperType | None = None,
+ sender: t.Any | None = None,
+ /,
+ *,
+ _async_wrapper: PAsyncWrapper | None = None,
**kwargs: t.Any,
- ) -> list[tuple[t.Callable, t.Any]]:
+ ) -> list[tuple[t.Callable[..., t.Any], t.Any]]:
"""Emit this signal on behalf of *sender*, passing on ``kwargs``.
Returns a list of 2-tuples, pairing receivers with their return
@@ -297,23 +279,29 @@ class Signal:
if self.is_muted:
return []
- sender = self._extract_sender(sender)
results = []
+
for receiver in self.receivers_for(sender):
if iscoroutinefunction(receiver):
if _async_wrapper is None:
- raise RuntimeError("Cannot send to a coroutine function")
- receiver = _async_wrapper(receiver)
- result = receiver(sender, **kwargs)
+ raise RuntimeError("Cannot send to a coroutine function.")
+
+ result = _async_wrapper(receiver)(sender, **kwargs)
+ else:
+ result = receiver(sender, **kwargs)
+
results.append((receiver, result))
+
return results
async def send_async(
self,
- *sender: t.Any,
- _sync_wrapper: SyncWrapperType | None = None,
+ sender: t.Any | None = None,
+ /,
+ *,
+ _sync_wrapper: PSyncWrapper | None = None,
**kwargs: t.Any,
- ) -> list[tuple[t.Callable, t.Any]]:
+ ) -> list[tuple[t.Callable[..., t.Any], t.Any]]:
"""Emit this signal on behalf of *sender*, passing on ``kwargs``.
Returns a list of 2-tuples, pairing receivers with their return
@@ -329,39 +317,20 @@ class Signal:
if self.is_muted:
return []
- sender = self._extract_sender(sender)
results = []
+
for receiver in self.receivers_for(sender):
if not iscoroutinefunction(receiver):
if _sync_wrapper is None:
- raise RuntimeError("Cannot send to a non-coroutine function")
- receiver = _sync_wrapper(receiver)
- result = await receiver(sender, **kwargs)
- results.append((receiver, result))
- return results
+ raise RuntimeError("Cannot send to a non-coroutine function.")
- def _extract_sender(self, sender: t.Any) -> t.Any:
- if not self.receivers:
- # Ensure correct signature even on no-op sends, disable with -O
- # for lowest possible cost.
- if __debug__ and sender and len(sender) > 1:
- raise TypeError(
- f"send() accepts only one positional argument, {len(sender)} given"
- )
- return []
+ result = await _sync_wrapper(receiver)(sender, **kwargs)
+ else:
+ result = await receiver(sender, **kwargs)
- # Using '*sender' rather than 'sender=None' allows 'sender' to be
- # used as a keyword argument- i.e. it's an invisible name in the
- # function signature.
- if len(sender) == 0:
- sender = None
- elif len(sender) > 1:
- raise TypeError(
- f"send() accepts only one positional argument, {len(sender)} given"
- )
- else:
- sender = sender[0]
- return sender
+ results.append((receiver, result))
+
+ return results
def has_receivers_for(self, sender: t.Any) -> bool:
"""True if there is probably a receiver for *sender*.
@@ -373,36 +342,48 @@ class Signal:
"""
if not self.receivers:
return False
+
if self._by_sender[ANY_ID]:
return True
+
if sender is ANY:
return False
- return hashable_identity(sender) in self._by_sender
+
+ return make_id(sender) in self._by_sender
def receivers_for(
self, sender: t.Any
- ) -> t.Generator[t.Callable[[t.Any], t.Any], None, None]:
+ ) -> t.Generator[t.Callable[..., t.Any], None, None]:
"""Iterate all live receivers listening for *sender*."""
# TODO: test receivers_for(ANY)
- if self.receivers:
- sender_id = hashable_identity(sender)
- if sender_id in self._by_sender:
- ids = self._by_sender[ANY_ID] | self._by_sender[sender_id]
- else:
- ids = self._by_sender[ANY_ID].copy()
- for receiver_id in ids:
- receiver = self.receivers.get(receiver_id)
- if receiver is None:
+ if not self.receivers:
+ return
+
+ sender_id = make_id(sender)
+
+ if sender_id in self._by_sender:
+ ids = self._by_sender[ANY_ID] | self._by_sender[sender_id]
+ else:
+ ids = self._by_sender[ANY_ID].copy()
+
+ for receiver_id in ids:
+ receiver = self.receivers.get(receiver_id)
+
+ if receiver is None:
+ continue
+
+ if isinstance(receiver, weakref.ref):
+ strong = receiver()
+
+ if strong is None:
+ self._disconnect(receiver_id, ANY_ID)
continue
- if isinstance(receiver, WeakTypes):
- strong = receiver()
- if strong is None:
- self._disconnect(receiver_id, ANY_ID)
- continue
- receiver = strong
- yield receiver # type: ignore[misc]
-
- def disconnect(self, receiver: t.Callable, sender: t.Any = ANY) -> None:
+
+ yield strong
+ else:
+ yield receiver
+
+ def disconnect(self, receiver: t.Callable[..., t.Any], sender: t.Any = ANY) -> None:
"""Disconnect *receiver* from this signal's events.
:param receiver: a previously :meth:`connected<connect>` callable
@@ -411,12 +392,14 @@ class Signal:
to disconnect from all senders. Defaults to ``ANY``.
"""
- sender_id: IdentityType
+ sender_id: t.Hashable
+
if sender is ANY:
sender_id = ANY_ID
else:
- sender_id = hashable_identity(sender)
- receiver_id = hashable_identity(receiver)
+ sender_id = make_id(sender)
+
+ receiver_id = make_id(receiver)
self._disconnect(receiver_id, sender_id)
if (
@@ -425,27 +408,40 @@ class Signal:
):
self.receiver_disconnected.send(self, receiver=receiver, sender=sender)
- def _disconnect(self, receiver_id: IdentityType, sender_id: IdentityType) -> None:
+ def _disconnect(self, receiver_id: t.Hashable, sender_id: t.Hashable) -> None:
if sender_id == ANY_ID:
- if self._by_receiver.pop(receiver_id, False):
+ if self._by_receiver.pop(receiver_id, None) is not None:
for bucket in self._by_sender.values():
bucket.discard(receiver_id)
+
self.receivers.pop(receiver_id, None)
else:
self._by_sender[sender_id].discard(receiver_id)
self._by_receiver[receiver_id].discard(sender_id)
- def _cleanup_receiver(self, receiver_ref: annotatable_weakref) -> None:
+ def _make_cleanup_receiver(
+ self, receiver_id: t.Hashable
+ ) -> t.Callable[[weakref.ref[t.Callable[..., t.Any]]], None]:
"""Disconnect a receiver from all senders."""
- self._disconnect(cast(IdentityType, receiver_ref.receiver_id), ANY_ID)
- def _cleanup_sender(self, sender_ref: annotatable_weakref) -> None:
+ def cleanup(ref: weakref.ref[t.Callable[..., t.Any]]) -> None:
+ self._disconnect(receiver_id, ANY_ID)
+
+ return cleanup
+
+ def _make_cleanup_sender(
+ self, sender_id: t.Hashable
+ ) -> t.Callable[[weakref.ref[t.Any]], None]:
"""Disconnect all receivers from a sender."""
- sender_id = cast(IdentityType, sender_ref.sender_id)
assert sender_id != ANY_ID
- self._weak_senders.pop(sender_id, None)
- for receiver_id in self._by_sender.pop(sender_id, ()):
- self._by_receiver[receiver_id].discard(sender_id)
+
+ def cleanup(ref: weakref.ref[t.Any]) -> None:
+ self._weak_senders.pop(sender_id, None)
+
+ for receiver_id in self._by_sender.pop(sender_id, ()):
+ self._by_receiver[receiver_id].discard(sender_id)
+
+ return cleanup
def _cleanup_bookkeeping(self) -> None:
"""Prune unused sender/receiver bookkeeping. Not threadsafe.
@@ -469,9 +465,9 @@ class Signal:
failure mode is perhaps not a big deal for you.
"""
for mapping in (self._by_sender, self._by_receiver):
- for _id, bucket in list(mapping.items()):
+ for ident, bucket in list(mapping.items()):
if not bucket:
- mapping.pop(_id, None)
+ mapping.pop(ident, None)
def _clear_state(self) -> None:
"""Throw away all signal state. Useful for unit tests."""
@@ -481,7 +477,7 @@ class Signal:
self._by_receiver.clear()
-receiver_connected = Signal(
+_receiver_connected = Signal(
"""\
Sent by a :class:`Signal` after a receiver connects.
@@ -491,12 +487,9 @@ Sent by a :class:`Signal` after a receiver connects.
:keyword weak_arg: true if the connection to receiver_arg is a weak reference
.. deprecated:: 1.2
-
-As of 1.2, individual signals have their own private
-:attr:`~Signal.receiver_connected` and
-:attr:`~Signal.receiver_disconnected` signals with a slightly simplified
-call signature. This global signal is planned to be removed in 1.6.
-
+ Individual signals have their own :attr:`~Signal.receiver_connected` and
+ :attr:`~Signal.receiver_disconnected` signals with a slightly simplified
+ call signature. This global signal will be removed in Blinker 1.9.
"""
)
@@ -505,24 +498,23 @@ class NamedSignal(Signal):
"""A named generic notification emitter."""
def __init__(self, name: str, doc: str | None = None) -> None:
- Signal.__init__(self, doc)
+ super().__init__(doc)
#: The name of this signal.
- self.name = name
+ self.name: str = name
def __repr__(self) -> str:
- base = Signal.__repr__(self)
+ base = super().__repr__()
return f"{base[:-1]}; {self.name!r}>" # noqa: E702
-class Namespace(dict):
+class Namespace(dict): # type: ignore[type-arg]
"""A mapping of signal names to signals."""
def signal(self, name: str, doc: str | None = None) -> NamedSignal:
"""Return the :class:`NamedSignal` *name*, creating it if required.
Repeated calls to this function will return the same signal object.
-
"""
try:
return self[name] # type: ignore[no-any-return]
@@ -531,7 +523,7 @@ class Namespace(dict):
return result # type: ignore[no-any-return]
-class WeakNamespace(WeakValueDictionary):
+class _WeakNamespace(WeakValueDictionary): # type: ignore[type-arg]
"""A weak mapping of signal names to signals.
Automatically cleans up unused Signals when the last reference goes out
@@ -540,8 +532,19 @@ class WeakNamespace(WeakValueDictionary):
.. versionadded:: 1.3
+ .. deprecated:: 1.3
+ Will be removed in Blinker 1.9.
"""
+ def __init__(self) -> None:
+ warnings.warn(
+ "'WeakNamespace' is deprecated and will be removed in Blinker 1.9."
+ " Use 'Namespace' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ super().__init__()
+
def signal(self, name: str, doc: str | None = None) -> NamedSignal:
"""Return the :class:`NamedSignal` *name*, creating it if required.
@@ -555,4 +558,34 @@ class WeakNamespace(WeakValueDictionary):
return result # type: ignore[no-any-return]
-signal = Namespace().signal
+default_namespace = Namespace()
+"""A default namespace for creating named signals. :func:`signal` creates a
+:class:`NamedSignal` in this namespace.
+"""
+
+signal = default_namespace.signal
+"""Create a :class:`NamedSignal` in :data:`default_namespace`. Repeated calls
+with the same name will return the same signal.
+"""
+
+
+def __getattr__(name: str) -> t.Any:
+ if name == "reciever_connected":
+ warnings.warn(
+ "The global 'reciever_connected' signal is deprecated and will be"
+ " removed in Blinker 1.9. Use 'Signal.receiver_connected' and"
+ " 'Signal.reciever_disconnected' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _receiver_connected
+
+ if name == "WeakNamespace":
+ warnings.warn(
+ "'WeakNamespace' is deprecated and will be removed in Blinker 1.9."
+ " Use 'Namespace' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ raise AttributeError(name)
diff --git a/contrib/python/blinker/py3/ya.make b/contrib/python/blinker/py3/ya.make
index f1ac7057e7..0520c07319 100644
--- a/contrib/python/blinker/py3/ya.make
+++ b/contrib/python/blinker/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(1.7.0)
+VERSION(1.8.0)
LICENSE(MIT)