Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ A high-performance, type-safe Mediator pattern implementation for Python 3.13+,
## Features

- **Result-Driven Development**: Built-in support for `flow-res` Result types, making error handling explicit and type-safe.
- **Auto-Registration**: Handlers are automatically registered using Python 3.13's `__init_subclass__` and generic introspection.
- **Auto-Registration**: Each `Mediator` discovers concrete handlers using Python 3.13's generic introspection.
- **Dependency Injection**: Seamless integration with the `injector` library for robust dependency management.
- **Native Async Support**: Designed from the ground up for `asyncio` with `AwaitableResult` support for elegant method chaining.
- **Strict Type Safety**: Fully compatible with `pyright` and `mypy` using modern Python 3.13 type parameters.
- **Strict Type Safety**: Public type contracts are checked by `pyright`/`mypy`, with result-type validation at handler registration.

## Installation

Expand All @@ -35,7 +35,7 @@ class GetUserRequest(Request[Result[str, Exception]]):

### 2. Implement Handler

Handlers are automatically registered when defined. Use `@override` to ensure correct implementation.
Concrete handlers are discovered by each `Mediator` instance. Use `@override` to ensure correct implementation.

```python
from typing import override
Expand All @@ -49,20 +49,20 @@ class GetUserHandler(RequestHandler[GetUserRequest, Result[str, Exception]]):
return Ok(f"User {request.user_id}")
```

### 3. Initialize and Send
### 3. Create a Mediator and Send

```python
import asyncio
from injector import Injector
from flow_med import Mediator

async def main():
# Initialize with an Injector
Mediator.initialize(Injector())
# Each application (or test) owns its Mediator and Injector.
mediator = Mediator(Injector())

# Send request and chain results using flow-res
result = await (
Mediator.send_async(GetUserRequest(user_id=1))
mediator.send_async(GetUserRequest(user_id=1))
.map(lambda name: f"Hello, {name}!")
.unwrap()
)
Expand Down
10 changes: 5 additions & 5 deletions examples/basic_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self, user_id: int):


# 2. Implement Handler
# Handlers are automatically registered when defined.
# Concrete handlers are discovered by each Mediator instance.
class GetUserHandler(RequestHandler[GetUserRequest, Result[str, Exception]]):
@override
async def handle(self, request: GetUserRequest) -> Result[str, Exception]:
Expand All @@ -23,17 +23,17 @@ async def handle(self, request: GetUserRequest) -> Result[str, Exception]:


async def main():
# 3. Initialize with an Injector
# In a real app, you would configure your modules here.
Mediator.initialize(Injector())
# 3. Create a Mediator with an Injector.
# In a real app, configure the Injector with your modules here.
mediator = Mediator(Injector())

print("--- Basic Usage Example ---")

# 4. Send request and chain results using flow-res
# Mediator.send_async returns an AwaitableResult,
# allowing you to chain operations before awaiting.
result = await (
Mediator.send_async(GetUserRequest(user_id=42))
mediator.send_async(GetUserRequest(user_id=42))
.map(lambda name: f"Hello, {name}!")
.unwrap()
)
Expand Down
8 changes: 4 additions & 4 deletions examples/di_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, user_id: int):


# 4. Implement Handler with DI
# RequestHandler is automatically registered.
# RequestHandler implementations are discovered by each Mediator instance.
class GetUserHandler(RequestHandler[GetUserRequest, Result[str, Exception]]):
# UserRepository is injected via the constructor
@inject
Expand All @@ -42,15 +42,15 @@ async def handle(self, request: GetUserRequest) -> Result[str, Exception]:


async def main():
# 5. Initialize with an Injector containing our Module
# 5. Create a Mediator with an Injector containing our Module
injector = Injector([UserModule()])
Mediator.initialize(injector)
mediator = Mediator(injector)

print("--- Dependency Injection Example ---")

# 6. Send request
result = await (
Mediator.send_async(GetUserRequest(user_id=123))
mediator.send_async(GetUserRequest(user_id=123))
.map(lambda name: f"Retrieved: {name}")
.unwrap()
)
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ source = "vcs"

[tool.hatch.build.targets.wheel]
packages = ["src/flow_med"]
force-include = { "src/flow_med/py.typed" = "flow_med/py.typed" }

[project.urls]
Homepage = "https://github.com/aiagate/flow-med"
Expand Down Expand Up @@ -76,3 +77,9 @@ addopts = [
"--cov-report=html",
]
asyncio_default_fixture_loop_scope = "function"

[tool.pyright]
include = ["src", "tests", "examples"]
exclude = ["tests/typecheck"]
venvPath = "."
venv = ".venv"
20 changes: 18 additions & 2 deletions src/flow_med/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
from .mediator import HandlerNotFoundError, Mediator, Request, RequestHandler
from .mediator import (
DuplicateHandlerError,
HandlerNotFoundError,
HandlerRegistrationError,
InvalidHandlerError,
Mediator,
Request,
RequestHandler,
)

__all__ = ["HandlerNotFoundError", "Mediator", "Request", "RequestHandler"]
__all__ = [
"DuplicateHandlerError",
"HandlerNotFoundError",
"HandlerRegistrationError",
"InvalidHandlerError",
"Mediator",
"Request",
"RequestHandler",
]
Loading