Skip to main content

Small publish/subscribe event bus with typed events, inspired by the CherryPy bus.

Events are plain classes that carry the payload type and the listener return type, so event payloads and results can be statically checked with mypy (the package ships a py.typed marker).

import trolleybus

class UserLoggedIn(trolleybus.Event[str, None]):
    pass

bus = trolleybus.EventBus()

@bus.subscribe(UserLoggedIn)
def greet(username: str) -> None:
    print(f'Hello, {username}!')

bus.broadcast(UserLoggedIn, 'kate')

Features

  • Typed event payloads and listener results (Event[Payload, Result])

  • Synchronous dispatch ordered by priority (higher value runs first, subscription order breaks ties)

  • broadcast, broadcast_nothrow, send_one, send_any emission styles

  • Class-based Subscriber with declarative @subscribe handlers bound to the bus lifecycle (OnStart / OnExit)

  • Emitter / EmitterMixin facades for components that only emit

  • Thread-safe subscription management and broadcasting

Installation

$ pip install trolleybus

Usage

Events

An event defines the payload type passed to the listeners and the type of the value they return. Events are never instantiated.

import trolleybus

class UserLoggedIn(trolleybus.Event[str, None]):
    """payload: username, listeners return nothing"""

class ResolveConfig(trolleybus.Event[str, dict]):
    """payload: config key, listeners return the resolved value"""

If no name is given, the class name is used.

Subscribing

bus = trolleybus.EventBus()

# decorator form
@bus.subscribe(UserLoggedIn, priority=60)
def greet(username: str) -> None:
    ...

# direct form
def audit(username: str) -> None:
    ...

bus.subscribe(UserLoggedIn, audit, priority=40)
bus.unsubscribe(UserLoggedIn, audit)

Subscribing the same listener twice only updates its priority.

Emitting

# call all listeners, collect results, propagate listener errors
bus.broadcast(UserLoggedIn, 'kate')

# call all listeners, capture errors as ListenerResult(value, error)
for result in bus.broadcast_nothrow(UserLoggedIn, 'kate'):
    if not result.ok:
        print('listener failed:', result.error)

# call only the highest priority listener (NoListenersError if none)
config = bus.send_one(ResolveConfig, 'database.url')

# call listeners until one returns a non-None value
config = bus.send_any(ResolveConfig, 'database.url')

The bus exposes two lifecycle events: bus.start() emits OnStart and then OnStarted, bus.stop() emits OnExit.

Class-based subscribers

class Metrics(trolleybus.Subscriber):
    @trolleybus.subscribe(UserLoggedIn)
    def on_user_logged_in(self, username: str) -> None:
        ...

metrics = Metrics(bus)
bus.start()  # handlers attach
bus.stop()   # handlers detach; they re-attach on the next start

Emitters

class LoginService(trolleybus.Subscriber, trolleybus.EmitterMixin):
    def login(self, username: str) -> None:
        self.broadcast(UserLoggedIn, username)

A standalone emitter also exists: emitter = trolleybus.Emitter(bus).

Development

$ poetry install
$ poetry run pytest tests
$ poetry run mypy trolleybus
$ poetry run pylint trolleybus

Metadata

Release files for trolleybus 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for trolleybus 0.2.0
File Size Uploaded Provenance Actions
trolleybus-0.2.0.tar.gz 7.3 kB With provenance Details for trolleybus-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for trolleybus 0.2.0
File Interpreter ABI Platform Actions
trolleybus-0.2.0-py3-none-any.whl Python 3 none any Details for trolleybus-0.2.0-py3-none-any.whl

Total release size: 16.7 kB

Release files / trolleybus-0.2.0.tar.gz

Download URL trolleybus-0.2.0.tar.gz
Size 7.3 kB
Tags Source
SHA-256 checksum
How to use checksums
74a7ee47b74dec461d27b932f0af26a34fd4c64e3ebdc3967346ad9e7a83032e
BLAKE2b-256 checksum
How to use checksums
9b9e60446a3ea3ed01dd2633ab4f281b7fd73ba77b756881ea695bf81ca68d14
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release files / trolleybus-0.2.0-py3-none-any.whl

Download URL trolleybus-0.2.0-py3-none-any.whl
Size 9.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
36e014879cd0f6fbfadcb897a2ff7a46076508a81dd564e2a8d3961be4c05f91
BLAKE2b-256 checksum
How to use checksums
f118da66cf0353037386546ab99ef630dca6c9a0c5c4db9d3e7d007c9d2fc574
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Security for trolleybus 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for trolleybus-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page