# Blinker Documentation

Blinker provides fast & simple object-to-object and broadcast signaling for Python objects.

The core of Blinker is quite small but provides powerful features:

> - a global registry of named signals

> - anonymous signals

> - custom name registries

> - permanently or temporarily connected receivers

> - automatically disconnected receivers via weak referencing

> - sending arbitrary data payloads

> - collecting return values from signal receivers

> - thread safety

Blinker was written by Jason Kirtand and is provided under the MIT License. The library supports Python 3.8 or later; or PyPy3.9 or later.

## Decoupling With Named Signals

Named signals are created with `signal()`:

    >>> from blinker import signal
    >>> initialized = signal('initialized')
    >>> initialized is signal('initialized')
    True

Every call to `signal('name')` returns the same signal object, allowing unconnected parts of code (different modules, plugins, anything) to all use the same signal without requiring any code sharing or special imports.

## Subscribing to Signals

`Signal.connect()` registers a function to be invoked each time the signal is emitted. Connected functions are always passed the object that caused the signal to be emitted.

    >>> def subscriber(sender):
    ...     print(f"Got a signal sent by {sender!r}")
    ...
    >>> ready = signal('ready')
    >>> ready.connect(subscriber)
    <function subscriber at 0x...>

## Emitting Signals

Code producing events of interest can `Signal.send()` notifications to all connected receivers.

Below, a simple `Processor` class emits a `ready` signal when it’s about to process something, and `complete` when it is done. It passes `self` to the `send()` method, signifying that that particular instance was responsible for emitting the signal.

    >>> class Processor:
    ...    def __init__(self, name):
    ...        self.name = name
    ...
    ...    def go(self):
    ...        ready = signal('ready')
    ...        ready.send(self)
    ...        print("Processing.")
    ...        complete = signal('complete')
    ...        complete.send(self)
    ...
    ...    def __repr__(self):
    ...        return f'<Processor {self.name}>'
    ...
    >>> processor_a = Processor('a')
    >>> processor_a.go()
    Got a signal sent by <Processor a>
    Processing.

Notice the `complete` signal in `go()`? No receivers have connected to `complete` yet, and that’s a-ok. Calling `send()` on a signal with no receivers will result in no notifications being sent, and these no-op sends are optimized to be as inexpensive as possible.

## Subscribing to Specific Senders

The default connection to a signal invokes the receiver function when any sender emits it. The `Signal.connect()` function accepts an optional argument to restrict the subscription to one specific sending object:

    >>> def b_subscriber(sender):
    ...     print("Caught signal from processor_b.")
    ...     assert sender.name == 'b'
    ...
    >>> processor_b = Processor('b')
    >>> ready.connect(b_subscriber, sender=processor_b)
    <function b_subscriber at 0x...>

This function has been subscribed to `ready` but only when sent by `processor_b`:

    >>> processor_a.go()
    Got a signal sent by <Processor a>
    Processing.
    >>> processor_b.go()
    Got a signal sent by <Processor b>
    Caught signal from processor_b.
    Processing.

## Sending and Receiving Data Through Signals

Additional keyword arguments can be passed to `send()`. These will in turn be passed to the connected functions:

    >>> send_data = signal('send-data')
    >>> @send_data.connect
    ... def receive_data(sender, **kw):
    ...     print(f"Caught signal from {sender!r}, data {kw!r}")
    ...     return 'received!'
    ...
    >>> result = send_data.send('anonymous', abc=123)
    Caught signal from 'anonymous', data {'abc': 123}

The return value of `send()` collects the return values of each connected function as a list of (`receiver function`, `return value`) pairs:

    >>> result
    [(<function receive_data at 0x...>, 'received!')]

## Muting signals

To mute a signal, as may be required when testing, the `muted()` can be used as a context decorator:

    sig = signal('send-data')
    with sig.muted():
         ...

## Anonymous Signals

Signals need not be named. The `Signal` constructor creates a unique signal each time it is invoked. For example, an alternative implementation of the Processor from above might provide the processing signals as class attributes:

    >>> from blinker import Signal
    >>> class AltProcessor:
    ...    on_ready = Signal()
    ...    on_complete = Signal()
    ...
    ...    def __init__(self, name):
    ...        self.name = name
    ...
    ...    def go(self):
    ...        self.on_ready.send(self)
    ...        print("Alternate processing.")
    ...        self.on_complete.send(self)
    ...
    ...    def __repr__(self):
    ...        return f'<AltProcessor {self.name}>'
    ...

## `connect` as a Decorator

You may have noticed the return value of `connect()` in the console output in the sections above. This allows `connect` to be used as a decorator on functions:

    >>> apc = AltProcessor('c')
    >>> @apc.on_complete.connect
    ... def completed(sender):
    ...     print f"AltProcessor {sender.name} completed!"
    ...
    >>> apc.go()
    Alternate processing.
    AltProcessor c completed!

While convenient, this form unfortunately does not allow the `sender` or `weak` arguments to be customized for the connected function. For this, `connect_via()` can be used:

    >>> dice_roll = signal('dice_roll')
    >>> @dice_roll.connect_via(1)
    ... @dice_roll.connect_via(3)
    ... @dice_roll.connect_via(5)
    ... def odd_subscriber(sender):
    ...     print(f"Observed dice roll {sender!r}.")
    ...
    >>> result = dice_roll.send(3)
    Observed dice roll 3.

## Optimizing Signal Sending

Signals are optimized to send very quickly, whether receivers are connected or not. If the keyword data to be sent with a signal is expensive to compute, it can be more efficient to check to see if any receivers are connected first by testing the `receivers` property:

    >>> bool(signal('ready').receivers)
    True
    >>> bool(signal('complete').receivers)
    False
    >>> bool(AltProcessor.on_complete.receivers)
    True

Checking for a receiver listening for a particular sender is also possible:

    >>> signal('ready').has_receivers_for(processor_a)
    True

## Documenting Signals

Both named and anonymous signals can be passed a `doc` argument at construction to set the pydoc help text for the signal. This documentation will be picked up by most documentation generators (such as sphinx) and is nice for documenting any additional data parameters that will be sent down with the signal.

## Async receivers

Receivers can be coroutine functions which can be called and awaited via the `send_async()` method:

    sig = blinker.Signal()

    async def receiver():
        ...

    sig.connect(receiver)
    await sig.send_async()

This however requires that all receivers are awaitable which then precludes the usage of `send()`. To mix and match the `send_async()` method takes a `_sync_wrapper` argument such as:

    sig = blinker.Signal()

    def receiver():
        ...

    sig.connect(receiver)

    def wrapper(func):

        async def inner(*args, **kwargs):
            func(*args, **kwargs)

        return inner

    await sig.send_async(_sync_wrapper=wrapper)

The equivalent usage for `send()` is via the `_async_wrapper` argument. This usage is will depend on your event loop, and in the simple case whereby you aren’t running within an event loop the following example can be used:

    sig = blinker.Signal()

    async def receiver():
        ...

    sig.connect(receiver)

    def wrapper(func):

        def inner(*args, **kwargs):
            asyncio.run(func(*args, **kwargs))

        return inner

    await sig.send(_async_wrapper=wrapper)

## Call receivers in order of registration

It can be advantageous to call a signal’s receivers in the order they were registered. To achieve this the storage class for receivers should be changed from an (unordered) set to an ordered set,

    from blinker import Signal
    from ordered_set import OrderedSet

    Signal.set_class = OrderedSet

Please note that `ordered_set` is a PyPI package and is not installed with blinker.

## API Documentation

All public API members can (and should) be imported from `blinker`:

    from blinker import ANY, signal

### Basic Signals

blinker.ANY

  Symbol for “any sender”.

class blinker.Signal(doc=None)

  A notification emitter.

  Parameters:

    doc (str | None) – The docstring for the signal.

  set_class

    alias of `set`

  connect(receiver, sender=ANY, weak=True)

    Connect `receiver` to be called when the signal is sent by `sender`.

    Parameters:

      - receiver (F) – The callable to call when `send()` is called with the given `sender`, passing `sender` as a positional argument along with any extra keyword arguments.

      - sender (Any) – Any object or `ANY`. `receiver` will only be called when `send()` is called with this sender. If `ANY`, the receiver will be called for any sender. A receiver may be connected to multiple senders by calling `connect()` multiple times.

      - weak (bool) – Track the receiver with a `weakref`. The receiver will be automatically disconnected when it is garbage collected. When connecting a receiver defined within a function, set to `False`, otherwise it will be disconnected when the function scope ends.

    Return type:

      F

  connect_via(sender, weak=False)

    Connect the decorated function to be called when the signal is sent by `sender`.

    The decorated function will be called when `send()` is called with the given `sender`, passing `sender` as a positional argument along with any extra keyword arguments.

    Parameters:

      - sender (Any) – Any object or `ANY`. `receiver` will only be called when `send()` is called with this sender. If `ANY`, the receiver will be called for any sender. A receiver may be connected to multiple senders by calling `connect()` multiple times.

      - weak (bool) – Track the receiver with a `weakref`. The receiver will be automatically disconnected when it is garbage collected. When connecting a receiver defined within a function, set to `False`, otherwise it will be disconnected when the function scope ends.=

    Return type:

      Callable[[F], F]

    Changelog

    Added in version 1.1.

  connected_to(receiver, sender=ANY)

    A context manager that temporarily connects `receiver` to the signal while a `with` block executes. When the block exits, the receiver is disconnected. Useful for tests.

    Parameters:

      - receiver (Callable[[...], Any]) – The callable to call when `send()` is called with the given `sender`, passing `sender` as a positional argument along with any extra keyword arguments.

      - sender (Any) – Any object or `ANY`. `receiver` will only be called when `send()` is called with this sender. If `ANY`, the receiver will be called for any sender.

    Return type:

      Generator[None, None, None]

    Changelog

    Added in version 1.1.

  disconnect(receiver, sender=ANY)

    Disconnect `receiver` from being called when the signal is sent by `sender`.

    Parameters:

      - receiver (Callable[[...], Any]) – A connected receiver callable.

      - sender (Any) – Disconnect from only this sender. By default, disconnect from all senders.

    Return type:

      None

  has_receivers_for(sender)

    Check if there is at least one receiver that will be called with the given `sender`. A receiver connected to `ANY` will always be called, regardless of sender. Does not check if weakly referenced receivers are still live. See `receivers_for()` for a stronger search.

    Parameters:

      sender (Any) – Check for receivers connected to this sender, in addition to those connected to `ANY`.

    Return type:

      bool

  muted()

    A context manager that temporarily disables the signal. No receivers will be called if the signal is sent, until the `with` block exits. Useful for tests.

    Return type:

      Generator[None, None, None]

  receivers_for(sender)

    Yield each receiver to be called for `sender`, in addition to those to be called for `ANY`. Weakly referenced receivers that are not live will be disconnected and skipped.

    Parameters:

      sender (Any) – Yield receivers connected to this sender, in addition to those connected to `ANY`.

    Return type:

      Generator[Callable[[…], Any], None, None]

  send(sender=None, /, *, _async_wrapper=None, **kwargs)

    Call all receivers that are connected to the given `sender` or `ANY`. Each receiver is called with `sender` as a positional argument along with any extra keyword arguments. Return a list of `(receiver, return value)` tuples.

    The order receivers are called is undefined, but can be influenced by setting `set_class`.

    If a receiver raises an exception, that exception will propagate up. This makes debugging straightforward, with an assumption that correctly implemented receivers will not raise.

    Parameters:

      - sender (Any | None) – Call receivers connected to this sender, in addition to those connected to `ANY`.

      - _async_wrapper (Callable[[Callable[[...], Coroutine[Any, Any, Any]]], Callable[[...], Any]] | None) – Will be called on any receivers that are async coroutines to turn them into sync callables. For example, could run the receiver with an event loop.

      - kwargs (Any) – Extra keyword arguments to pass to each receiver.

    Return type:

      list[tuple[Callable[[…], Any], Any]]

    Changelog

    Changed in version 1.7: Added the `_async_wrapper` argument.

  async send_async(sender=None, /, *, _sync_wrapper=None, **kwargs)

    Await all receivers that are connected to the given `sender` or `ANY`. Each receiver is called with `sender` as a positional argument along with any extra keyword arguments. Return a list of `(receiver, return value)` tuples.

    The order receivers are called is undefined, but can be influenced by setting `set_class`.

    If a receiver raises an exception, that exception will propagate up. This makes debugging straightforward, with an assumption that correctly implemented receivers will not raise.

    Parameters:

      - sender (Any | None) – Call receivers connected to this sender, in addition to those connected to `ANY`.

      - _sync_wrapper (Callable[[Callable[[...], Any]], Callable[[...], Coroutine[Any, Any, Any]]] | None) – Will be called on any receivers that are sync callables to turn them into async coroutines. For example, could call the receiver in a thread.

      - kwargs (Any) – Extra keyword arguments to pass to each receiver.

    Return type:

      list[tuple[Callable[[…], Any], Any]]

    Changelog

    Added in version 1.7.

  ANY = ANY

    An alias for the `ANY` sender symbol.

  property receiver_connected: Signal

    Emitted at the end of each `connect()` call.

    The signal sender is the signal instance, and the `connect()` arguments are passed through: `receiver`, `sender`, and `weak`.

    Changelog

    Added in version 1.2.

  property receiver_disconnected: Signal

    Emitted at the end of each `disconnect()` call.

    The sender is the signal instance, and the `disconnect()` arguments are passed through: `receiver` and `sender`.

    This signal is emitted only when `disconnect()` is called explicitly. This signal cannot be emitted by an automatic disconnect when a weakly referenced receiver or sender goes out of scope, as the instance is no longer be available to be used as the sender for this signal.

    An alternative approach is available by subscribing to `receiver_connected` and setting up a custom weakref cleanup callback on weak receivers and senders.

    Changelog

    Added in version 1.2.

  receivers: dict[Any, ReferenceType[Callable[[...], Any]] | Callable[[...], Any]]

    The map of connected receivers. Useful to quickly check if any receivers are connected to the signal: `if s.receivers:`. The structure and data is not part of the public API, but checking its boolean value is.

### Named Signals

blinker.signal(name, doc=None)

  Return a `NamedSignal` in `default_namespace` for the given name, creating it if required. Repeated calls with the same name return the same signal.

  Parameters:

    - name (str) – The name of the signal.

    - doc (str | None) – The docstring of the signal.

  Return type:

    NamedSignal

blinker.default_namespace

  A default `Namespace` for creating named signals. `signal()` creates a `NamedSignal` in this namespace.

class blinker.NamedSignal(name, doc=None)

  Bases: `Signal`

  A named generic notification emitter. The name is not used by the signal itself, but matches the key in the `Namespace` that it belongs to.

  Parameters:

    - name (str) – The name of the signal within the namespace.

    - doc (str | None) – The docstring for the signal.

class blinker.Namespace

  Bases: `dict`[`str`, `NamedSignal`]

  A dict mapping names to signals.

  signal(name, doc=None)

    Return the `NamedSignal` for the given `name`, creating it if required. Repeated calls with the same name return the same signal.

    Parameters:

      - name (str) – The name of the signal.

      - doc (str | None) – The docstring of the signal.

    Return type:

      NamedSignal

# Changes

## Version 1.9.0

Released 2024-11-08

- Drop support for Python 3.8. #175

- Remove previously deprecated `__version__`, `receiver_connected`, `Signal.temporarily_connected_to` and `WeakNamespace`. #172

- Skip weakref signal cleanup if the interpreter is shutting down. #173

## Version 1.8.2

Released 2024-05-06

- Simplify type for `_async_wrapper` and `_sync_wrapper` arguments. #156

## Version 1.8.1

Released 2024-04-28

- Restore identity handling for `str` and `int` senders. #148

- Fix deprecated `blinker.base.WeakNamespace` import. #149

- Fix deprecated `blinker.base.receiver_connected import`. #153

- Use types from `collections.abc` instead of `typing`. #150

- Fully specify exported types as reported by pyright. #152

## Version 1.8.0

Released 2024-04-27

- Deprecate the `__version__` attribute. Use feature detection, or `importlib.metadata.version("blinker")`, instead. #128

- Specify that the deprecated `temporarily_connected_to` will be removed in the next version.

- Show a deprecation warning for the deprecated global `receiver_connected` signal and specify that it will be removed in the next version.

- Show a deprecation warning for the deprecated `WeakNamespace` and specify that it will be removed in the next version.

- Greatly simplify how the library uses weakrefs. This is a significant change internally but should not affect any public API. #144

- Expose the namespace used by `signal()` as `default_namespace`. #145

## Version 1.7.0

Released 2023-11-01

- Fixed messages printed to standard error about unraisable exceptions during signal cleanup, typically during interpreter shutdown. #123

- Allow the Signal `set_class` to be customised, to allow calling of receivers in registration order. #116.

- Drop Python 3.7 and support Python 3.12. #126

## Version 1.6.3

Released 2023-09-23

- Fix `SyncWrapperType` and `AsyncWrapperType` #108

- Fixed issue where `connected_to` would not disconnect the receiver if an instance of `BaseException` was raised. #114

## Version 1.6.2

Released 2023-04-12

- Type annotations are not evaluated at runtime. typing-extensions is not a runtime dependency. #94

## Version 1.6.1

Released 2023-04-09

- Ensure that `py.typed` is present in the distributions (to enable other projects to use Blinker’s typing).

- Require typing-extensions > 4.2 to ensure it includes `ParamSpec`. #90

## Version 1.6

Released 2023-04-02

- Add a `muted` context manager to temporarily turn off a signal. #84

- `int` instances with the same value will be treated as the same sender, the same as `str` instances. #83

- Add a `send_async` method to allow signals to send to coroutine receivers. #76

- Update and modernise the project structure to match that used by the Pallets projects. #77

- Add an initial set of type hints for the project.

## Version 1.5

Released 2022-07-17

- Support Python >= 3.7 and PyPy. Python 2, Python < 3.7, and Jython may continue to work, but the next release will make incompatible changes.

## Version 1.4

Released 2015-07-23

- Verified Python 3.4 support, no changes needed.

- Additional bookkeeping cleanup for non-`ANY` connections at disconnect time.

- Added `Signal._cleanup_bookeeping()` to prune stale bookkeeping on demand.

## Version 1.3

Released 2013-07-03

- The global signal stash behind `signal()` is now backed by a regular name-to-`Signal` dictionary. Previously, weak references were held in the mapping and ephermal usage in code like `signal('foo').connect(...)` could have surprising program behavior depending on import order of modules.

- `Namespace` is now built on a regular dict. Use `WeakNamespace` for the older, weak-referencing behavior.

- `Signal.connect('text-sender')` uses an alterate hashing strategy to avoid sharp edges in text identity.

## Version 1.2

Released 2011-10-26

- Added `Signal.receiver_connected` and `Signal.receiver_disconnected` per-`Signal` signals.

- Deprecated the global `receiver_connected` signal.

- Verified Python 3.2 support, no changes needed.

## Version 1.1

Released 2010-07-21

- Added `@signal.connect_via(sender)` decorator

- Added `signal.connected_to` shorthand name for the `temporarily_connected_to` context manager.

## Version 1.0

Released 2010-03-28

- Python 3.0 and 3.1 compatibility.

## Version 0.9

Released 2010-02-26

- Added `Signal.temporarily_connected_to` context manager.

- Docs! Sphinx docs, project web site.

## Version 0.8

Released 2010-02-14

- Initial release.

- Extracted from `flatland.util.signals`.

- Added Python 2.4 compatibility.

- Added nearly functional Python 3.1 compatibility. Everything except connecting to instance methods seems to work.

# MIT License

    Copyright 2010 Jason Kirtland

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the
    "Software"), to deal in the Software without restriction, including
    without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to
    the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.