Skip to content

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.

interface_example.py python
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.query
  • DatabaseService.result
  • DatabaseService.error
i Note
The method bodies use ... (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.

alphabet.py python
# 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.
action_params.py python
@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.

requires.py python
@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: ...
Important
Dependencies declared with @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.