Skip to main content

Lightweight async message-passing building blocks: an action bus and an event bus

Project description

lapwing

lapwing is a library providing lightweight async message-passing building blocks: an action bus and an event bus.

Why lapwing? The lapwing is a bird renowned for its piercing, far-carrying calls — it detects disturbances and signals them immediately to others nearby. This mirrors the library's purpose: one part of a system raises a signal, and the right listeners respond, without the sender knowing or caring who they are.

Core Concepts

The library addresses a common architectural need: decoupling the sender of a message from its handler. Instead of calling functions directly, you define typed messages and register handlers separately, keeping concerns isolated and code testable.

All operations are async-first and return asyncio.Task, giving the caller control over when to await.

Key Primitives

Action: A subclass carrying a phantom type parameter T — used only by the type checker to annotate what the handler returns. Define one per action.

Event: A subclass representing something that has happened. Multiple listeners may react to the same event.

ActionBus: Dispatches an action to exactly one registered async handler. Raises NoHandlerError eagerly if no handler is registered. Supports an optional middleware pipeline.

EventBus: Broadcasts an event to all registered async listeners concurrently via asyncio.TaskGroup. Succeeds silently if no listeners are registered. Raises ExceptionGroup if any listener fails.

Usage

ActionBus

Middlewares wrap the handler pipeline in list order — middlewares[0] is outermost.

NOTE: Middlewares apply to all action types on a bus and must preserve the return type of each concrete action's handler.

from dataclasses import dataclass

from lapwing import Action, ActionBus


@dataclass
class CreateUser(Action[int]):
    username: str
    email: str


async def logging_middleware(action, call_next):
    print(f"Dispatching {type(action).__name__}")
    result = await call_next(action)
    print("Done")
    return result


bus = ActionBus(middlewares=[logging_middleware])


@bus.handler(CreateUser)
async def handle_create_user(action: CreateUser) -> int:
    user = await db.insert(action.username, action.email)
    return user.id


user_id = await bus.dispatch(CreateUser(username="alice", email="alice@example.com"))

EventBus

from dataclasses import dataclass

from lapwing import Event, EventBus


@dataclass
class UserCreated(Event):
    user_id: int


bus = EventBus()


@bus.listener(UserCreated)
async def send_welcome_email(event: UserCreated) -> None:
    await mailer.send_welcome(event.user_id)


@bus.listener(UserCreated)
async def write_audit_log(event: UserCreated) -> None:
    await audit.log(f"User {event.user_id} created")


await bus.emit(UserCreated(user_id=42))

Installation

uv add lapwing

Requirements

Python 3.13+

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lapwing-0.1.0.tar.gz (18.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lapwing-0.1.0-py3-none-any.whl (5.4 kB view details)

Uploaded Python 3

File details

Details for the file lapwing-0.1.0.tar.gz.

File metadata

  • Download URL: lapwing-0.1.0.tar.gz
  • Upload date:
  • Size: 18.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for lapwing-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f1e7066c3eb4dd6bce27f3745ae6df1062add994f7ffb274948a315f6d8292d3
MD5 18825a247c2794f1281d28a38bbc9c5b
BLAKE2b-256 cc62cf2b70d4f890344bd368afffc1d27509f8483ed8b6cd951639778161a20b

See more details on using hashes here.

File details

Details for the file lapwing-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lapwing-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 5.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for lapwing-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1a92489b7b62b19b149aed0f132aaf617bdb2c139f0436128800295969dc428b
MD5 7e270063a15c76595e0b127deccf7fe5
BLAKE2b-256 1d41e31c0f32dfb398bde3f673c744c4225d942c65c36c4ce1145113e69dba2b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page