Modules
A module implements an interface with concrete behavior. While interfaces declare what events a component handles, modules define how it responds to them. Modules are the active, running units of a PyRapide system.
Defining a Module
Use the @module decorator with the implements parameter to bind a class
to an interface. The module provides lifecycle hooks and reactive event handlers.
1from pyrapide import module, when, get_context, Pattern
2
3@module(implements=DatabaseService)
4class PostgresModule:
5 async def start(self):
6 ctx = get_context(self)
7 ctx.generate_event("DatabaseService.query",
8 payload={"sql": "SELECT 1", "params": {}})
9
10 @when(Pattern.match("request.received"))
11 async def on_request(self, match):
12 ctx = get_context(self)
13 event = list(match.events)[0]
14 ctx.generate_event("DatabaseService.query",
15 payload={"sql": event.payload["sql"],
16 "params": event.payload.get("params", {})},
17 caused_by=list(match.events))
Lifecycle Hooks
Modules support two lifecycle hooks that run at predictable points in the system's execution:
start(): called when the module is initialized within an architecture. Use it for setup tasks like establishing connections, loading configuration, or emitting initial events.finish(): called when the module is shutting down. Use it for cleanup such as closing connections or flushing buffers.
@module(implements=DatabaseService)
class PostgresModule:
async def start(self):
# Initialize connection pool, emit readiness event
ctx = get_context(self)
self.pool = await create_pool(dsn="postgresql://...")
ctx.generate_event("DatabaseService.query",
payload={"sql": "SELECT 1", "params": {}})
async def finish(self):
# Gracefully close the connection pool
await self.pool.close()
Reactive Handlers with @when
The @when decorator registers a method as a reactive handler. When the specified pattern matches against incoming events, the handler is invoked with
the match result.
@module(implements=DatabaseService)
class PostgresModule:
@when(Pattern.match("request.received"))
async def on_request(self, match):
"""Handle incoming requests by issuing database queries."""
ctx = get_context(self)
event = list(match.events)[0]
ctx.generate_event("DatabaseService.query",
payload={"sql": event.payload["sql"],
"params": event.payload.get("params", {})},
caused_by=list(match.events))
@when(Pattern.match("DatabaseService.error"))
async def on_error(self, match):
"""Log and handle database errors."""
event = list(match.events)[0]
print(f"Database error: {event.payload['message']}")
@when handlers. Each handler reacts independently to
its own pattern, allowing you to separate concerns cleanly within a single module.Module Context
The get_context(self) function returns the module's runtime context, which provides:
generate_event(name, payload, caused_by): emit a new event into the system with optional causal links.computation: access the current Computation to query the causal graph.clock: access the module's clock for temporal reasoning.
Conformance Checking
ConformanceError at decoration time. This catches integration
mistakes before your system starts.@module(implements=DatabaseService)
class IncompleteModule:
async def start(self):
pass
# Missing handlers for query, result, error!
# Raises ConformanceError: IncompleteModule does not conform to
# DatabaseService - missing actions: query, result, error
The conformance check ensures that every action declared in the interface's alphabet has a corresponding handler in the module. This guarantee is enforced statically at decoration time, not at runtime.
To compose modules into a running system, see Architectures and Connections.