pymediate 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymediate-0.1.0/LICENSE +21 -0
- pymediate-0.1.0/PKG-INFO +238 -0
- pymediate-0.1.0/README.md +209 -0
- pymediate-0.1.0/pyproject.toml +79 -0
- pymediate-0.1.0/src/pymediate/__init__.py +106 -0
- pymediate-0.1.0/src/pymediate/_internal/__init__.py +10 -0
- pymediate-0.1.0/src/pymediate/_internal/handler.py +195 -0
- pymediate-0.1.0/src/pymediate/_internal/mediator.py +128 -0
- pymediate-0.1.0/src/pymediate/_internal/registry.py +421 -0
- pymediate-0.1.0/src/pymediate/aio/__init__.py +36 -0
- pymediate-0.1.0/src/pymediate/aio/handler.py +111 -0
- pymediate-0.1.0/src/pymediate/aio/mediator.py +196 -0
- pymediate-0.1.0/src/pymediate/aio/pipeline.py +366 -0
- pymediate-0.1.0/src/pymediate/errors.py +239 -0
- pymediate-0.1.0/src/pymediate/handler.py +108 -0
- pymediate-0.1.0/src/pymediate/mediator.py +170 -0
- pymediate-0.1.0/src/pymediate/pipeline.py +363 -0
- pymediate-0.1.0/src/pymediate/providers/__init__.py +9 -0
- pymediate-0.1.0/src/pymediate/providers/dependency_injector.py +288 -0
- pymediate-0.1.0/src/pymediate/py.typed +0 -0
- pymediate-0.1.0/src/pymediate/request.py +85 -0
- pymediate-0.1.0/src/pymediate/service.py +801 -0
pymediate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 sina-al
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pymediate-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pymediate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A type-safe mediator pattern implementation for Python 3.12+
|
|
5
|
+
Keywords: mediator,cqrs,dependency-injection,type-safe,request-dispatch
|
|
6
|
+
Author: sina-al
|
|
7
|
+
Author-email: sina-al <27771210+sina-al@users.noreply.github.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: dependency-injector>=4.41.0 ; extra == 'di'
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Project-URL: Homepage, https://github.com/sina-al/pymediate
|
|
23
|
+
Project-URL: Documentation, https://sina-al.github.io/pymediate/
|
|
24
|
+
Project-URL: Repository, https://github.com/sina-al/pymediate
|
|
25
|
+
Project-URL: Issues, https://github.com/sina-al/pymediate/issues
|
|
26
|
+
Project-URL: Changelog, https://github.com/sina-al/pymediate/releases
|
|
27
|
+
Provides-Extra: di
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="https://github.com/sina-al/pymediate/blob/main/assets/logo.svg?raw=true" alt="PyMediate logo" width="400"><br><br>
|
|
32
|
+
<b>A type-safe request mediator for Python 3.12+</b><br><br>
|
|
33
|
+
|
|
34
|
+
<!-- Badges -->
|
|
35
|
+
<a href="https://github.com/sina-al/pymediate/actions/workflows/test.yml">
|
|
36
|
+
<img src="https://github.com/sina-al/pymediate/actions/workflows/test.yml/badge.svg" alt="Tests">
|
|
37
|
+
</a>
|
|
38
|
+
<a href="https://github.com/sina-al/pymediate/actions/workflows/code-quality.yml">
|
|
39
|
+
<img src="https://github.com/sina-al/pymediate/actions/workflows/code-quality.yml/badge.svg" alt="Code Quality">
|
|
40
|
+
</a>
|
|
41
|
+
<a href="https://www.python.org/downloads/">
|
|
42
|
+
<img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+">
|
|
43
|
+
</a>
|
|
44
|
+
<a href="https://badge.fury.io/py/pymediate">
|
|
45
|
+
<img src="https://badge.fury.io/py/pymediate.svg" alt="PyPI version">
|
|
46
|
+
</a>
|
|
47
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
48
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License">
|
|
49
|
+
</a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- **Type Safety**: Full runtime validation with mypy support
|
|
57
|
+
- **Zero Convention**: No naming conventions - uses type inspection
|
|
58
|
+
- **Async/Await Support**: First-class async handlers and mediators via `pymediate.aio`
|
|
59
|
+
- **DI Ready**: Built-in dependency-injector integration
|
|
60
|
+
- **Dataclass Friendly**: Works seamlessly with `@dataclass` and Request[T] inheritance
|
|
61
|
+
- **Well Tested**: comprehensive test suite
|
|
62
|
+
|
|
63
|
+
## Quick Example
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from dataclasses import dataclass
|
|
67
|
+
from pymediate import Request, Handler, Mediator, Services
|
|
68
|
+
|
|
69
|
+
# Define response and request as pure dataclasses
|
|
70
|
+
@dataclass
|
|
71
|
+
class UserCreated:
|
|
72
|
+
user_id: int
|
|
73
|
+
username: str
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class CreateUser(Request[UserCreated]):
|
|
77
|
+
username: str
|
|
78
|
+
email: str
|
|
79
|
+
|
|
80
|
+
# Handler automatically linked by type
|
|
81
|
+
class CreateUserHandler(Handler[CreateUser]):
|
|
82
|
+
def __call__(self, req: CreateUser) -> UserCreated:
|
|
83
|
+
return UserCreated(user_id=1, username=req.username)
|
|
84
|
+
|
|
85
|
+
# Set up and use
|
|
86
|
+
services = Services()
|
|
87
|
+
services.add(CreateUserHandler())
|
|
88
|
+
provider = services.provider()
|
|
89
|
+
mediator = Mediator(provider)
|
|
90
|
+
|
|
91
|
+
response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
92
|
+
print(f"User {response.username} created with ID {response.user_id}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Async Support
|
|
96
|
+
|
|
97
|
+
PyMediate provides first-class async/await support through the `pymediate.aio` package:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import asyncio
|
|
101
|
+
from dataclasses import dataclass
|
|
102
|
+
from pymediate import Request, Services
|
|
103
|
+
from pymediate.aio import Handler, Mediator
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class UserCreated:
|
|
107
|
+
user_id: int
|
|
108
|
+
username: str
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class CreateUser(Request[UserCreated]):
|
|
112
|
+
username: str
|
|
113
|
+
email: str
|
|
114
|
+
|
|
115
|
+
class CreateUserHandler(Handler[CreateUser]):
|
|
116
|
+
async def __call__(self, req: CreateUser) -> UserCreated:
|
|
117
|
+
# Perform async operations
|
|
118
|
+
await asyncio.sleep(0.1) # Simulate async database call
|
|
119
|
+
return UserCreated(user_id=1, username=req.username)
|
|
120
|
+
|
|
121
|
+
async def main():
|
|
122
|
+
services = Services()
|
|
123
|
+
services.add(CreateUserHandler())
|
|
124
|
+
provider = services.provider()
|
|
125
|
+
mediator = Mediator(provider)
|
|
126
|
+
|
|
127
|
+
response = await mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
128
|
+
print(f"User {response.username} created with ID {response.user_id}")
|
|
129
|
+
|
|
130
|
+
asyncio.run(main())
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Key differences for async:**
|
|
134
|
+
- Import from `pymediate.aio` instead of `pymediate`
|
|
135
|
+
- Handler's `__call__` method must be `async def`
|
|
136
|
+
- Use `await mediator.send(...)` instead of `mediator.send(...)`
|
|
137
|
+
- Supports concurrent request handling with `asyncio.gather()`
|
|
138
|
+
|
|
139
|
+
### Pipeline Behaviors
|
|
140
|
+
|
|
141
|
+
PyMediate supports pipeline behaviors (middleware) that automatically wrap request processing for cross-cutting concerns like logging, validation, caching, and more:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from pymediate import Request, PipelineBehavior
|
|
145
|
+
|
|
146
|
+
# Universal behavior - applies to all requests
|
|
147
|
+
class LoggingBehavior(PipelineBehavior[Request]):
|
|
148
|
+
def __call__(self, request, next):
|
|
149
|
+
print(f"Handling: {type(request).__name__}")
|
|
150
|
+
response = next()
|
|
151
|
+
print(f"Completed: {type(request).__name__}")
|
|
152
|
+
return response
|
|
153
|
+
|
|
154
|
+
# Selective behavior - only applies to CreateUser requests
|
|
155
|
+
class ValidationBehavior(PipelineBehavior[CreateUser]):
|
|
156
|
+
def __call__(self, request, next):
|
|
157
|
+
# Validate before processing
|
|
158
|
+
if not request.username:
|
|
159
|
+
raise ValueError("Username is required")
|
|
160
|
+
return next()
|
|
161
|
+
|
|
162
|
+
# Register behaviors and handlers
|
|
163
|
+
services = Services()
|
|
164
|
+
services.add(LoggingBehavior()) # Applied to ALL requests
|
|
165
|
+
services.add(ValidationBehavior()) # Only applied to CreateUser
|
|
166
|
+
services.add(CreateUserHandler())
|
|
167
|
+
|
|
168
|
+
mediator = Mediator(services.provider())
|
|
169
|
+
|
|
170
|
+
# Behaviors automatically wrap matching requests
|
|
171
|
+
response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
172
|
+
# Output:
|
|
173
|
+
# Handling: CreateUser
|
|
174
|
+
# Completed: CreateUser
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Behaviors can be **universal** (`PipelineBehavior[Request]`) or **selective** (`PipelineBehavior[SpecificRequest]`), applying only to matching request types or mixins. They are resolved per request, respecting DI container scopes (Transient, Scoped, Singleton). See the [Pipeline Behaviors Guide](https://sina-al.github.io/pymediate/guide/pipeline-behaviors/) for more examples.
|
|
178
|
+
|
|
179
|
+
## Installation
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Core package
|
|
183
|
+
pip install pymediate
|
|
184
|
+
|
|
185
|
+
# With dependency injection support
|
|
186
|
+
pip install pymediate[di]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Documentation
|
|
190
|
+
|
|
191
|
+
**[📚 Full Documentation](https://sina-al.github.io/pymediate/)**
|
|
192
|
+
|
|
193
|
+
- [Quick Start](https://sina-al.github.io/pymediate/getting-started/quick-start/)
|
|
194
|
+
- [User Guide](https://sina-al.github.io/pymediate/guide/requests-responses/)
|
|
195
|
+
- [Examples](https://sina-al.github.io/pymediate/examples/basic/)
|
|
196
|
+
- [API Reference](https://sina-al.github.io/pymediate/api/request/)
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
### Quick Start
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Clone and install
|
|
204
|
+
git clone https://github.com/sina-al/pymediate.git
|
|
205
|
+
cd pymediate
|
|
206
|
+
uv sync --all-extras --group test
|
|
207
|
+
|
|
208
|
+
# Run tests
|
|
209
|
+
poe test
|
|
210
|
+
|
|
211
|
+
# Run all checks
|
|
212
|
+
poe check:all
|
|
213
|
+
|
|
214
|
+
# See all available tasks
|
|
215
|
+
poe
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Available Commands
|
|
219
|
+
|
|
220
|
+
PyMediate uses [Poe the Poet](https://poethepoet.natn.io/) for task running. Run `poe` to see all commands, or check [`tasks.toml`](tasks.toml).
|
|
221
|
+
|
|
222
|
+
> **Note:** `uv sync` alone only installs the default `dev` dependency group (ruff, mypy,
|
|
223
|
+
> poethepoet). Test dependencies (pytest and friends) live in the separate `test` group and
|
|
224
|
+
> won't be installed unless you pass `--group test` (or `--all-groups`) — otherwise `poe test`
|
|
225
|
+
> fails with `Failed to spawn: pytest`.
|
|
226
|
+
|
|
227
|
+
## Requirements
|
|
228
|
+
|
|
229
|
+
- Python 3.12+
|
|
230
|
+
- Optional: `dependency-injector>=4.41.0` for DI support
|
|
231
|
+
|
|
232
|
+
## Contributing
|
|
233
|
+
|
|
234
|
+
Contributions are welcome! Check the docs for guidelines.
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://github.com/sina-al/pymediate/blob/main/assets/logo.svg?raw=true" alt="PyMediate logo" width="400"><br><br>
|
|
3
|
+
<b>A type-safe request mediator for Python 3.12+</b><br><br>
|
|
4
|
+
|
|
5
|
+
<!-- Badges -->
|
|
6
|
+
<a href="https://github.com/sina-al/pymediate/actions/workflows/test.yml">
|
|
7
|
+
<img src="https://github.com/sina-al/pymediate/actions/workflows/test.yml/badge.svg" alt="Tests">
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://github.com/sina-al/pymediate/actions/workflows/code-quality.yml">
|
|
10
|
+
<img src="https://github.com/sina-al/pymediate/actions/workflows/code-quality.yml/badge.svg" alt="Code Quality">
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://www.python.org/downloads/">
|
|
13
|
+
<img src="https://img.shields.io/badge/python-3.12+-blue.svg" alt="Python 3.12+">
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://badge.fury.io/py/pymediate">
|
|
16
|
+
<img src="https://badge.fury.io/py/pymediate.svg" alt="PyPI version">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://opensource.org/licenses/MIT">
|
|
19
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License">
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- **Type Safety**: Full runtime validation with mypy support
|
|
28
|
+
- **Zero Convention**: No naming conventions - uses type inspection
|
|
29
|
+
- **Async/Await Support**: First-class async handlers and mediators via `pymediate.aio`
|
|
30
|
+
- **DI Ready**: Built-in dependency-injector integration
|
|
31
|
+
- **Dataclass Friendly**: Works seamlessly with `@dataclass` and Request[T] inheritance
|
|
32
|
+
- **Well Tested**: comprehensive test suite
|
|
33
|
+
|
|
34
|
+
## Quick Example
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from dataclasses import dataclass
|
|
38
|
+
from pymediate import Request, Handler, Mediator, Services
|
|
39
|
+
|
|
40
|
+
# Define response and request as pure dataclasses
|
|
41
|
+
@dataclass
|
|
42
|
+
class UserCreated:
|
|
43
|
+
user_id: int
|
|
44
|
+
username: str
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class CreateUser(Request[UserCreated]):
|
|
48
|
+
username: str
|
|
49
|
+
email: str
|
|
50
|
+
|
|
51
|
+
# Handler automatically linked by type
|
|
52
|
+
class CreateUserHandler(Handler[CreateUser]):
|
|
53
|
+
def __call__(self, req: CreateUser) -> UserCreated:
|
|
54
|
+
return UserCreated(user_id=1, username=req.username)
|
|
55
|
+
|
|
56
|
+
# Set up and use
|
|
57
|
+
services = Services()
|
|
58
|
+
services.add(CreateUserHandler())
|
|
59
|
+
provider = services.provider()
|
|
60
|
+
mediator = Mediator(provider)
|
|
61
|
+
|
|
62
|
+
response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
63
|
+
print(f"User {response.username} created with ID {response.user_id}")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Async Support
|
|
67
|
+
|
|
68
|
+
PyMediate provides first-class async/await support through the `pymediate.aio` package:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
from dataclasses import dataclass
|
|
73
|
+
from pymediate import Request, Services
|
|
74
|
+
from pymediate.aio import Handler, Mediator
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class UserCreated:
|
|
78
|
+
user_id: int
|
|
79
|
+
username: str
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class CreateUser(Request[UserCreated]):
|
|
83
|
+
username: str
|
|
84
|
+
email: str
|
|
85
|
+
|
|
86
|
+
class CreateUserHandler(Handler[CreateUser]):
|
|
87
|
+
async def __call__(self, req: CreateUser) -> UserCreated:
|
|
88
|
+
# Perform async operations
|
|
89
|
+
await asyncio.sleep(0.1) # Simulate async database call
|
|
90
|
+
return UserCreated(user_id=1, username=req.username)
|
|
91
|
+
|
|
92
|
+
async def main():
|
|
93
|
+
services = Services()
|
|
94
|
+
services.add(CreateUserHandler())
|
|
95
|
+
provider = services.provider()
|
|
96
|
+
mediator = Mediator(provider)
|
|
97
|
+
|
|
98
|
+
response = await mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
99
|
+
print(f"User {response.username} created with ID {response.user_id}")
|
|
100
|
+
|
|
101
|
+
asyncio.run(main())
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Key differences for async:**
|
|
105
|
+
- Import from `pymediate.aio` instead of `pymediate`
|
|
106
|
+
- Handler's `__call__` method must be `async def`
|
|
107
|
+
- Use `await mediator.send(...)` instead of `mediator.send(...)`
|
|
108
|
+
- Supports concurrent request handling with `asyncio.gather()`
|
|
109
|
+
|
|
110
|
+
### Pipeline Behaviors
|
|
111
|
+
|
|
112
|
+
PyMediate supports pipeline behaviors (middleware) that automatically wrap request processing for cross-cutting concerns like logging, validation, caching, and more:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from pymediate import Request, PipelineBehavior
|
|
116
|
+
|
|
117
|
+
# Universal behavior - applies to all requests
|
|
118
|
+
class LoggingBehavior(PipelineBehavior[Request]):
|
|
119
|
+
def __call__(self, request, next):
|
|
120
|
+
print(f"Handling: {type(request).__name__}")
|
|
121
|
+
response = next()
|
|
122
|
+
print(f"Completed: {type(request).__name__}")
|
|
123
|
+
return response
|
|
124
|
+
|
|
125
|
+
# Selective behavior - only applies to CreateUser requests
|
|
126
|
+
class ValidationBehavior(PipelineBehavior[CreateUser]):
|
|
127
|
+
def __call__(self, request, next):
|
|
128
|
+
# Validate before processing
|
|
129
|
+
if not request.username:
|
|
130
|
+
raise ValueError("Username is required")
|
|
131
|
+
return next()
|
|
132
|
+
|
|
133
|
+
# Register behaviors and handlers
|
|
134
|
+
services = Services()
|
|
135
|
+
services.add(LoggingBehavior()) # Applied to ALL requests
|
|
136
|
+
services.add(ValidationBehavior()) # Only applied to CreateUser
|
|
137
|
+
services.add(CreateUserHandler())
|
|
138
|
+
|
|
139
|
+
mediator = Mediator(services.provider())
|
|
140
|
+
|
|
141
|
+
# Behaviors automatically wrap matching requests
|
|
142
|
+
response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
143
|
+
# Output:
|
|
144
|
+
# Handling: CreateUser
|
|
145
|
+
# Completed: CreateUser
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Behaviors can be **universal** (`PipelineBehavior[Request]`) or **selective** (`PipelineBehavior[SpecificRequest]`), applying only to matching request types or mixins. They are resolved per request, respecting DI container scopes (Transient, Scoped, Singleton). See the [Pipeline Behaviors Guide](https://sina-al.github.io/pymediate/guide/pipeline-behaviors/) for more examples.
|
|
149
|
+
|
|
150
|
+
## Installation
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Core package
|
|
154
|
+
pip install pymediate
|
|
155
|
+
|
|
156
|
+
# With dependency injection support
|
|
157
|
+
pip install pymediate[di]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Documentation
|
|
161
|
+
|
|
162
|
+
**[📚 Full Documentation](https://sina-al.github.io/pymediate/)**
|
|
163
|
+
|
|
164
|
+
- [Quick Start](https://sina-al.github.io/pymediate/getting-started/quick-start/)
|
|
165
|
+
- [User Guide](https://sina-al.github.io/pymediate/guide/requests-responses/)
|
|
166
|
+
- [Examples](https://sina-al.github.io/pymediate/examples/basic/)
|
|
167
|
+
- [API Reference](https://sina-al.github.io/pymediate/api/request/)
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
### Quick Start
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Clone and install
|
|
175
|
+
git clone https://github.com/sina-al/pymediate.git
|
|
176
|
+
cd pymediate
|
|
177
|
+
uv sync --all-extras --group test
|
|
178
|
+
|
|
179
|
+
# Run tests
|
|
180
|
+
poe test
|
|
181
|
+
|
|
182
|
+
# Run all checks
|
|
183
|
+
poe check:all
|
|
184
|
+
|
|
185
|
+
# See all available tasks
|
|
186
|
+
poe
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Available Commands
|
|
190
|
+
|
|
191
|
+
PyMediate uses [Poe the Poet](https://poethepoet.natn.io/) for task running. Run `poe` to see all commands, or check [`tasks.toml`](tasks.toml).
|
|
192
|
+
|
|
193
|
+
> **Note:** `uv sync` alone only installs the default `dev` dependency group (ruff, mypy,
|
|
194
|
+
> poethepoet). Test dependencies (pytest and friends) live in the separate `test` group and
|
|
195
|
+
> won't be installed unless you pass `--group test` (or `--all-groups`) — otherwise `poe test`
|
|
196
|
+
> fails with `Failed to spawn: pytest`.
|
|
197
|
+
|
|
198
|
+
## Requirements
|
|
199
|
+
|
|
200
|
+
- Python 3.12+
|
|
201
|
+
- Optional: `dependency-injector>=4.41.0` for DI support
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
Contributions are welcome! Check the docs for guidelines.
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pymediate"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A type-safe mediator pattern implementation for Python 3.12+"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "sina-al", email = "27771210+sina-al@users.noreply.github.com" },
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
dependencies = []
|
|
13
|
+
keywords = ["mediator", "cqrs", "dependency-injection", "type-safe", "request-dispatch"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/sina-al/pymediate"
|
|
29
|
+
Documentation = "https://sina-al.github.io/pymediate/"
|
|
30
|
+
Repository = "https://github.com/sina-al/pymediate"
|
|
31
|
+
Issues = "https://github.com/sina-al/pymediate/issues"
|
|
32
|
+
Changelog = "https://github.com/sina-al/pymediate/releases"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
di = [
|
|
36
|
+
"dependency-injector>=4.41.0"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"ruff>=0.8.4",
|
|
42
|
+
"poethepoet>=0.31.1",
|
|
43
|
+
"mypy>=1.13.0",
|
|
44
|
+
]
|
|
45
|
+
test = [
|
|
46
|
+
"mypy>=1.13.0",
|
|
47
|
+
"pytest>=8.0.0",
|
|
48
|
+
"pytest-cov>=4.1.0",
|
|
49
|
+
"pytest-github-actions-annotate-failures>=0.2.0",
|
|
50
|
+
"pytest-asyncio>=1.2.0",
|
|
51
|
+
"pytest-xdist>=3.8.0",
|
|
52
|
+
]
|
|
53
|
+
docs = [
|
|
54
|
+
"mkdocs>=1.6.0",
|
|
55
|
+
"mkdocs-material>=9.5.0",
|
|
56
|
+
"mkdocstrings[python]>=0.26.0",
|
|
57
|
+
"mkdocs-git-revision-date-localized-plugin>=1.2.0",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[build-system]
|
|
61
|
+
requires = ["uv_build>=0.11.26,<0.12"]
|
|
62
|
+
build-backend = "uv_build"
|
|
63
|
+
|
|
64
|
+
[tool.uv]
|
|
65
|
+
# Single source of truth for the uv version: read locally by `uv` itself (errors on mismatch)
|
|
66
|
+
# and automatically by astral-sh/setup-uv in CI (falls back to pyproject.toml when no `version:`
|
|
67
|
+
# input is given). Bump with `uv run poe update-uv` — don't hand-edit.
|
|
68
|
+
required-version = "==0.11.26"
|
|
69
|
+
|
|
70
|
+
# TestPyPI staging index — release.yml publishes here (`uv publish --index testpypi`) before
|
|
71
|
+
# the real PyPI publish, as a dry run against the exact same Trusted Publishing mechanism.
|
|
72
|
+
[[tool.uv.index]]
|
|
73
|
+
name = "testpypi"
|
|
74
|
+
url = "https://test.pypi.org/simple/"
|
|
75
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
76
|
+
explicit = true
|
|
77
|
+
|
|
78
|
+
[tool.poe]
|
|
79
|
+
include = "tasks.toml"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""PyMediate - A type-safe mediator pattern implementation for Python.
|
|
2
|
+
|
|
3
|
+
PyMediate is a modern implementation of the Mediator Pattern that provides
|
|
4
|
+
type-safe request routing with automatic response type inference. It's designed
|
|
5
|
+
for Python 3.12+ and integrates seamlessly with dataclasses and dependency
|
|
6
|
+
injection frameworks.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- Type-safe: Full runtime validation with mypy support
|
|
10
|
+
- Zero convention: Uses type inspection instead of naming conventions
|
|
11
|
+
- Async/await support: Built-in async handlers and mediators via pymediate.aio
|
|
12
|
+
- DI ready: Built-in dependency-injector integration
|
|
13
|
+
- Dataclass friendly: Works seamlessly with @dataclass and Request[T]
|
|
14
|
+
- Well tested: 95%+ coverage enforced in CI
|
|
15
|
+
|
|
16
|
+
Quick Example:
|
|
17
|
+
```python
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pymediate import Request, Handler, Mediator, Services
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class UserCreated:
|
|
23
|
+
user_id: int
|
|
24
|
+
username: str
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CreateUser(Request[UserCreated]):
|
|
28
|
+
username: str
|
|
29
|
+
email: str
|
|
30
|
+
|
|
31
|
+
class CreateUserHandler(Handler[CreateUser]):
|
|
32
|
+
def __call__(self, req: CreateUser) -> UserCreated:
|
|
33
|
+
return UserCreated(user_id=1, username=req.username)
|
|
34
|
+
|
|
35
|
+
services = Services()
|
|
36
|
+
services.add(CreateUserHandler())
|
|
37
|
+
provider = services.provider()
|
|
38
|
+
mediator = Mediator(provider)
|
|
39
|
+
|
|
40
|
+
response = mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
41
|
+
print(f"User {response.username} created with ID {response.user_id}")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Main Components:
|
|
45
|
+
- Request: Base class for all requests
|
|
46
|
+
- Handler: Base class for synchronous handlers
|
|
47
|
+
- Mediator: Routes requests to handlers (sync version)
|
|
48
|
+
- ServiceProvider: Protocol for resolving service instances
|
|
49
|
+
- Services: Builder for registering services
|
|
50
|
+
|
|
51
|
+
Async Support:
|
|
52
|
+
For asynchronous operations, use the async variants from pymediate.aio:
|
|
53
|
+
```python
|
|
54
|
+
from pymediate import Services
|
|
55
|
+
from pymediate.aio import Handler, Mediator
|
|
56
|
+
|
|
57
|
+
class AsyncHandler(Handler[CreateUser]):
|
|
58
|
+
async def __call__(self, req: CreateUser) -> UserCreated:
|
|
59
|
+
# Can use await here
|
|
60
|
+
result = await async_database_operation(req)
|
|
61
|
+
return UserCreated(user_id=result.id, username=req.username)
|
|
62
|
+
|
|
63
|
+
services = Services()
|
|
64
|
+
services.add(AsyncHandler())
|
|
65
|
+
provider = services.provider()
|
|
66
|
+
mediator = Mediator(provider)
|
|
67
|
+
response = await mediator.send(CreateUser(username="alice", email="alice@example.com"))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For more information, see the documentation at https://sina-al.github.io/pymediate/
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
from .errors import (
|
|
74
|
+
HandlerAlreadyRegisteredError,
|
|
75
|
+
HandlerNotFoundError,
|
|
76
|
+
InvalidHandlerSignatureError,
|
|
77
|
+
InvalidRequestTypeError,
|
|
78
|
+
PyMediateError,
|
|
79
|
+
ResponseTypeMismatchError,
|
|
80
|
+
)
|
|
81
|
+
from .handler import Handler
|
|
82
|
+
from .mediator import Mediator
|
|
83
|
+
from .pipeline import PipelineBehavior
|
|
84
|
+
from .request import Request
|
|
85
|
+
from .service import ServiceNotFoundError, ServiceProvider, Services
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"Request",
|
|
89
|
+
"Handler",
|
|
90
|
+
"Mediator",
|
|
91
|
+
# Service Provider
|
|
92
|
+
"ServiceProvider",
|
|
93
|
+
"Services",
|
|
94
|
+
"ServiceNotFoundError",
|
|
95
|
+
# Pipeline
|
|
96
|
+
"PipelineBehavior",
|
|
97
|
+
# Errors
|
|
98
|
+
"PyMediateError",
|
|
99
|
+
"HandlerNotFoundError",
|
|
100
|
+
"HandlerAlreadyRegisteredError",
|
|
101
|
+
"InvalidHandlerSignatureError",
|
|
102
|
+
"InvalidRequestTypeError",
|
|
103
|
+
"ResponseTypeMismatchError",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Internal implementation details for PyMediate.
|
|
2
|
+
|
|
3
|
+
This package contains internal utilities, base classes, and registries that are
|
|
4
|
+
not part of the public API. Users should not import from this package directly.
|
|
5
|
+
|
|
6
|
+
Warning:
|
|
7
|
+
The contents of this package are internal implementation details and may
|
|
8
|
+
change without notice in any release. Only import from the main `pymediate`
|
|
9
|
+
package or `pymediate.aio` for public APIs.
|
|
10
|
+
"""
|