diff options
| author | robot-piglet <[email protected]> | 2024-05-13 00:00:59 +0300 | 
|---|---|---|
| committer | robot-piglet <[email protected]> | 2024-05-13 00:06:28 +0300 | 
| commit | d6b290c39d078c44e88669fcde8da65fb4d2a03f (patch) | |
| tree | 27b34ae7cbc09cec622689aea9222252cecc18cf /contrib/python | |
| parent | d502666ab38ad7f8b15bc6e839fd00a4d07cd274 (diff) | |
Intermediate changes
Diffstat (limited to 'contrib/python')
| -rw-r--r-- | contrib/python/blinker/py3/.dist-info/METADATA | 84 | ||||
| -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.md | 43 | ||||
| -rw-r--r-- | contrib/python/blinker/py3/README.rst | 40 | ||||
| -rw-r--r-- | contrib/python/blinker/py3/blinker/__init__.py | 61 | ||||
| -rw-r--r-- | contrib/python/blinker/py3/blinker/_utilities.py | 107 | ||||
| -rw-r--r-- | contrib/python/blinker/py3/blinker/base.py | 365 | ||||
| -rw-r--r-- | contrib/python/blinker/py3/ya.make | 2 | 
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 f96613c4b8a..2eb62efbef0 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 <[email protected]> +Author: Jason Kirtland  Maintainer-email: Pallets Ecosystem <[email protected]>  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 79c9825adba..79c9825adba 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 00000000000..a3969bbbe0f --- /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 e3bc6d47813..00000000000 --- 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 d014caa0ff2..c93527eca8b 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 4b711c67d3e..784ba4e121d 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 b9d703586de..8aa65255b9d 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 f1ac7057e72..0520c073193 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)  | 
