Interfaces and Actions
An interface in PyRapide declares the set of event types, called actions, that a component can produce or consume. Think of it as a contract: any module that implements an interface guarantees it can handle the declared actions.
Declaring an Interface
Use the @interface decorator on a class and mark each event type with @action. The method signatures define the payload shape for each event.
1from pyrapide import interface, action, requires
2
3@interface
4class DatabaseService:
5 @action
6 async def query(self, sql: str, params: dict) -> None: ...
7 @action
8 async def result(self, data: list) -> None: ...
9 @action
10 async def error(self, message: str) -> None: ...
The @interface decorator processes the class and builds an event
alphabet, a formal set of event names namespaced by the interface name. In this case,
the alphabet contains:
DatabaseService.queryDatabaseService.resultDatabaseService.error
... (Ellipsis) because interfaces only declare the event
shape. Concrete behavior is provided by Modules that implement
the interface.Event Alphabets
The event alphabet is central to PyRapide's type-safety story. When a module claims to implement an interface, PyRapide verifies at decoration time that every action in the alphabet has a corresponding handler. This catches wiring errors before your system runs.
# Inspect the alphabet programmatically
print(DatabaseService.__alphabet__)
# {'DatabaseService.query', 'DatabaseService.result', 'DatabaseService.error'}
# Check if an event name belongs to this interface
assert "DatabaseService.query" in DatabaseService.__alphabet__
The @action Decorator
Each @action method defines the payload schema for that event type. The method
parameters (excluding self) map directly to payload fields:
- Parameter names become payload keys.
- Type annotations are used for validation at runtime.
- Default values become optional payload fields.
@interface
class NotificationService:
@action
async def send(self, recipient: str, message: str, priority: int = 0) -> None: ...
# This defines an event "NotificationService.send" with payload:
# {"recipient": str, "message": str, "priority": int (default 0)}
The @requires Decorator
Use @requires to declare that an interface depends on events from another interface.
This creates explicit dependency relationships between components.
@interface
class AuthService:
@action
async def login(self, username: str, token: str) -> None: ...
@action
async def logout(self, username: str) -> None: ...
@interface
@requires(AuthService)
class UserDashboard:
@action
async def load(self, user_id: str) -> None: ...
@action
async def refresh(self, user_id: str) -> None: ...
@requires are checked when building an Architecture. If a required interface is not connected,
PyRapide will raise an error at composition time.Now that you have declared what events a component handles, see Modules to learn how to implement the concrete behavior.