This guide covers everything from basic setup to advanced pipeline orchestration.
Messages are simple dataclasses that represent an intent.
Commands represent an action that changes state. They typically do not return a value.
from pyline import Command
from dataclasses import dataclass
@dataclass
class RegisterUserCommand(Command):
username: str
email: str
Queries represent a request for information. They must return a result of a specific type by inheriting from Query[TResult].
from pyline import Query
from dataclasses import dataclass
@dataclass
class GetUserQuery(Query[dict]):
user_id: int
Handlers contain the actual business logic. Query handlers inherit from QueryHandler[TQuery, TResult].
from pyline import CommandHandler, QueryHandler
class RegisterUserHandler(CommandHandler):
async def handle(self, command: RegisterUserCommand) -> None:
# DB logic here...
print(f"User {command.username} registered.")
class GetUserHandler(QueryHandler[GetUserQuery, dict]):
async def handle(self, query: GetUserQuery) -> dict:
# DB retrieval here...
return {"id": query.user_id, "username": "alp"}
The mediator is the bridge between your messages and handlers. You can register handlers either by decorating them with @mediator.register or registering them manually.
from pyline import mediator
@mediator.register(RegisterUserCommand)
class RegisterUserHandler(CommandHandler):
async def handle(self, command: RegisterUserCommand) -> None:
pass
@mediator.register(GetUserQuery)
class GetUserHandler(QueryHandler[GetUserQuery, dict]):
async def handle(self, query: GetUserQuery) -> dict:
return {"id": query.user_id}
Dependencies (such as repositories, services, or configurations) can be passed directly into @mediator.register(...) as positional or keyword arguments. They will be passed to the handler’s constructor:
user_repo = UserRepository()
@mediator.register(RegisterUserCommand, repository=user_repo)
class RegisterUserHandler(CommandHandler):
def __init__(self, repository: UserRepository):
self.repository = repository
async def handle(self, command: RegisterUserCommand) -> None:
await self.repository.save(command.username, command.email)
user_repo = UserRepository()
mediator.register_handler(RegisterUserCommand, RegisterUserHandler(repository=user_repo))
mediator.register_handler(GetUserQuery, GetUserHandler())
async def run():
# mediator.send is fully typed and knows RegisterUserCommand returns None
await mediator.send(RegisterUserCommand("alp", "alp@example.com"))
# mediator.send is fully typed and knows GetUserQuery returns a dict
user = await mediator.send(GetUserQuery(1))
## 4. Advanced Pipelines
Pipelines are the most powerful feature of PyLine. They allow you to chain multiple steps.
```python
from pyline.pipe import Pipe
pipe = Pipe(
name="Full Registration Flow",
context={"username": "new_user", "email": "new@site.com"},
steps=[
RegisterUserCommand,
GetUserQuery # Assume this takes username from context
]
)
await pipe.run()
If a step returns a result, it is automatically merged into the pipeline’s context. Subsequent steps can then use these new values. The result can be of the following types:
dict)__dict____slots__If a step returns any other type or if required arguments for a step are missing from the context, a PipelineError will be fırst raised to help you debug quickly.
PyLine Core includes a lightweight EventBus to decouple components and handle side-effects asynchronously without blocking the main execution.
The EventBus supports subclass propagation. This means if you subscribe a handler to a base event class (such as BaseEvent), it will automatically trigger for all published subclasses of that event type:
from pyline import BaseEvent, EventHandler, EventBus
from dataclasses import dataclass
import asyncio
@dataclass(frozen=True, kw_only=True)
class UserCreatedEvent(BaseEvent):
user_id: int
name: str
# EmailNotificationHandler listens to UserCreatedEvent
class EmailNotificationHandler(EventHandler[UserCreatedEvent]):
async def handle(self, event: UserCreatedEvent) -> None:
print(f"Sending welcome email to User {event.user_id} ({event.name})")
# GeneralLogger listens to all system events inheriting from BaseEvent
class GeneralLogger(EventHandler[BaseEvent]):
async def handle(self, event: BaseEvent) -> None:
print(f"Logging event {event.event_id} of type {type(event).__name__}")
async def run_event_bus():
bus = EventBus()
bus.subscribe(UserCreatedEvent, EmailNotificationHandler())
bus.subscribe(BaseEvent, GeneralLogger()) # Will also catch UserCreatedEvent!
# Publish event (runs in the background)
bus.publish(UserCreatedEvent(user_id=1, name="Alp"))
# Gracefully shut down and wait for all background tasks
await bus.shutdown()