gridflow-python-mediator 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.
- gridflow_python_mediator-0.1.0/PKG-INFO +196 -0
- gridflow_python_mediator-0.1.0/README.md +180 -0
- gridflow_python_mediator-0.1.0/pyproject.toml +41 -0
- gridflow_python_mediator-0.1.0/setup.cfg +4 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator/__init__.py +19 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator/behaviors/__init__.py +17 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator/behaviors/observability.py +85 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator/behaviors/protocol.py +36 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator/behaviors/validation_behavior.py +38 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator/mediator.py +110 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator.egg-info/PKG-INFO +196 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator.egg-info/SOURCES.txt +14 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator.egg-info/dependency_links.txt +1 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator.egg-info/requires.txt +7 -0
- gridflow_python_mediator-0.1.0/src/gridflow_python_mediator.egg-info/top_level.txt +1 -0
- gridflow_python_mediator-0.1.0/tests/test_mediator.py +105 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gridflow-python-mediator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generic mediator pattern with pipeline behaviors
|
|
5
|
+
Author-email: GridFlow Team <vialogue@proton.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/firstunicorn/python-web-toolkit
|
|
8
|
+
Project-URL: Repository, https://github.com/firstunicorn/python-web-toolkit
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Provides-Extra: logging
|
|
12
|
+
Requires-Dist: structlog>=23.0.0; extra == "logging"
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# gridflow-python-mediator
|
|
18
|
+
|
|
19
|
+
Generic mediator pattern implementation with zero dependencies and pipeline behavior support.
|
|
20
|
+
|
|
21
|
+
**Extracted from:** GridFlow `backend/src/apps/token_generator/application/common/mediator/`
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Core (zero dependencies)
|
|
27
|
+
pip install gridflow-python-mediator
|
|
28
|
+
|
|
29
|
+
# With logging support
|
|
30
|
+
pip install gridflow-python-mediator[logging]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Zero Dependencies**: Pure Python implementation
|
|
36
|
+
- **Pipeline Behaviors**: Logging, timing, validation
|
|
37
|
+
- **Type-Safe**: Full generic type support
|
|
38
|
+
- **Async/Sync**: Supports both async and sync handlers
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Basic Mediator
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from gridflow_python_mediator import Mediator
|
|
46
|
+
|
|
47
|
+
# Define request and handler
|
|
48
|
+
class GetUserRequest:
|
|
49
|
+
def __init__(self, user_id: int):
|
|
50
|
+
self.user_id = user_id
|
|
51
|
+
|
|
52
|
+
class GetUserHandler:
|
|
53
|
+
async def handle(self, request: GetUserRequest):
|
|
54
|
+
# Fetch user logic
|
|
55
|
+
return {"id": request.user_id, "name": "John"}
|
|
56
|
+
|
|
57
|
+
# Setup mediator
|
|
58
|
+
mediator = Mediator()
|
|
59
|
+
mediator.register_handler(GetUserRequest, GetUserHandler())
|
|
60
|
+
|
|
61
|
+
# Send request
|
|
62
|
+
request = GetUserRequest(user_id=1)
|
|
63
|
+
result = await mediator.send(request)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With CQRS
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from python_cqrs_core import BaseCommand, ICommandHandler
|
|
70
|
+
from gridflow_python_mediator import Mediator
|
|
71
|
+
|
|
72
|
+
class CreateUserCommand(BaseCommand):
|
|
73
|
+
name: str
|
|
74
|
+
email: str
|
|
75
|
+
|
|
76
|
+
class CreateUserHandler(ICommandHandler[CreateUserCommand, int]):
|
|
77
|
+
async def handle(self, command: CreateUserCommand) -> int:
|
|
78
|
+
# Create user
|
|
79
|
+
return user_id
|
|
80
|
+
|
|
81
|
+
mediator = Mediator()
|
|
82
|
+
mediator.register_handler(CreateUserCommand, CreateUserHandler())
|
|
83
|
+
|
|
84
|
+
cmd = CreateUserCommand(name="John", email="john@example.com")
|
|
85
|
+
user_id = await mediator.send(cmd)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Pipeline Behaviors
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from gridflow_python_mediator import (
|
|
92
|
+
Mediator,
|
|
93
|
+
LoggingBehavior,
|
|
94
|
+
TimingBehavior,
|
|
95
|
+
ValidationBehavior
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
mediator = Mediator()
|
|
99
|
+
|
|
100
|
+
# Add behaviors (executed in order)
|
|
101
|
+
mediator.add_pipeline_behavior(LoggingBehavior().handle)
|
|
102
|
+
mediator.add_pipeline_behavior(TimingBehavior().handle)
|
|
103
|
+
mediator.add_pipeline_behavior(ValidationBehavior().handle)
|
|
104
|
+
|
|
105
|
+
# Behaviors run before handler
|
|
106
|
+
result = await mediator.send(request)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Custom Behavior
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
async def auth_behavior(request, handler):
|
|
113
|
+
"""Check authentication before handler."""
|
|
114
|
+
if not request.user_id:
|
|
115
|
+
raise ValueError("Unauthorized")
|
|
116
|
+
return None # Continue to handler
|
|
117
|
+
|
|
118
|
+
mediator.add_pipeline_behavior(auth_behavior)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API Reference
|
|
122
|
+
|
|
123
|
+
### `Mediator`
|
|
124
|
+
|
|
125
|
+
Generic request/response dispatcher.
|
|
126
|
+
|
|
127
|
+
**Methods:**
|
|
128
|
+
|
|
129
|
+
#### `register_handler(request_type, handler)`
|
|
130
|
+
|
|
131
|
+
Register handler for request type.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
- `request_type` (Type): Request class
|
|
135
|
+
- `handler` (Any): Handler instance with `handle()` method
|
|
136
|
+
|
|
137
|
+
#### `async send(request) -> TResult`
|
|
138
|
+
|
|
139
|
+
Dispatch request to handler.
|
|
140
|
+
|
|
141
|
+
**Parameters:**
|
|
142
|
+
- `request` (TRequest): Request to send
|
|
143
|
+
|
|
144
|
+
**Returns:**
|
|
145
|
+
- Result from handler
|
|
146
|
+
|
|
147
|
+
#### `add_pipeline_behavior(behavior)`
|
|
148
|
+
|
|
149
|
+
Add pipeline behavior.
|
|
150
|
+
|
|
151
|
+
**Parameters:**
|
|
152
|
+
- `behavior` (Callable): Async function(request, handler) -> Optional[result]
|
|
153
|
+
|
|
154
|
+
### Built-in Behaviors
|
|
155
|
+
|
|
156
|
+
#### `LoggingBehavior`
|
|
157
|
+
|
|
158
|
+
Logs request type before/after handler.
|
|
159
|
+
|
|
160
|
+
**Requires:** `structlog>=23.0.0`
|
|
161
|
+
|
|
162
|
+
#### `TimingBehavior`
|
|
163
|
+
|
|
164
|
+
Measures and logs execution duration.
|
|
165
|
+
|
|
166
|
+
**Requires:** `structlog>=23.0.0`
|
|
167
|
+
|
|
168
|
+
#### `ValidationBehavior`
|
|
169
|
+
|
|
170
|
+
Validates Pydantic models before handler.
|
|
171
|
+
|
|
172
|
+
## Design Patterns
|
|
173
|
+
|
|
174
|
+
### Mediator Pattern
|
|
175
|
+
|
|
176
|
+
Decouples senders from receivers:
|
|
177
|
+
- Single point of dispatch
|
|
178
|
+
- Handler registration
|
|
179
|
+
- Pipeline behaviors
|
|
180
|
+
|
|
181
|
+
### Pipeline Pattern
|
|
182
|
+
|
|
183
|
+
Behaviors execute in order:
|
|
184
|
+
1. Logging
|
|
185
|
+
2. Timing
|
|
186
|
+
3. Validation
|
|
187
|
+
4. Handler
|
|
188
|
+
|
|
189
|
+
## Dependencies
|
|
190
|
+
|
|
191
|
+
- **Core**: None (zero dependencies!)
|
|
192
|
+
- **Optional**: `structlog>=23.0.0` (for logging behaviors)
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# gridflow-python-mediator
|
|
2
|
+
|
|
3
|
+
Generic mediator pattern implementation with zero dependencies and pipeline behavior support.
|
|
4
|
+
|
|
5
|
+
**Extracted from:** GridFlow `backend/src/apps/token_generator/application/common/mediator/`
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Core (zero dependencies)
|
|
11
|
+
pip install gridflow-python-mediator
|
|
12
|
+
|
|
13
|
+
# With logging support
|
|
14
|
+
pip install gridflow-python-mediator[logging]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Zero Dependencies**: Pure Python implementation
|
|
20
|
+
- **Pipeline Behaviors**: Logging, timing, validation
|
|
21
|
+
- **Type-Safe**: Full generic type support
|
|
22
|
+
- **Async/Sync**: Supports both async and sync handlers
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Basic Mediator
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from gridflow_python_mediator import Mediator
|
|
30
|
+
|
|
31
|
+
# Define request and handler
|
|
32
|
+
class GetUserRequest:
|
|
33
|
+
def __init__(self, user_id: int):
|
|
34
|
+
self.user_id = user_id
|
|
35
|
+
|
|
36
|
+
class GetUserHandler:
|
|
37
|
+
async def handle(self, request: GetUserRequest):
|
|
38
|
+
# Fetch user logic
|
|
39
|
+
return {"id": request.user_id, "name": "John"}
|
|
40
|
+
|
|
41
|
+
# Setup mediator
|
|
42
|
+
mediator = Mediator()
|
|
43
|
+
mediator.register_handler(GetUserRequest, GetUserHandler())
|
|
44
|
+
|
|
45
|
+
# Send request
|
|
46
|
+
request = GetUserRequest(user_id=1)
|
|
47
|
+
result = await mediator.send(request)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### With CQRS
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from python_cqrs_core import BaseCommand, ICommandHandler
|
|
54
|
+
from gridflow_python_mediator import Mediator
|
|
55
|
+
|
|
56
|
+
class CreateUserCommand(BaseCommand):
|
|
57
|
+
name: str
|
|
58
|
+
email: str
|
|
59
|
+
|
|
60
|
+
class CreateUserHandler(ICommandHandler[CreateUserCommand, int]):
|
|
61
|
+
async def handle(self, command: CreateUserCommand) -> int:
|
|
62
|
+
# Create user
|
|
63
|
+
return user_id
|
|
64
|
+
|
|
65
|
+
mediator = Mediator()
|
|
66
|
+
mediator.register_handler(CreateUserCommand, CreateUserHandler())
|
|
67
|
+
|
|
68
|
+
cmd = CreateUserCommand(name="John", email="john@example.com")
|
|
69
|
+
user_id = await mediator.send(cmd)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Pipeline Behaviors
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from gridflow_python_mediator import (
|
|
76
|
+
Mediator,
|
|
77
|
+
LoggingBehavior,
|
|
78
|
+
TimingBehavior,
|
|
79
|
+
ValidationBehavior
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
mediator = Mediator()
|
|
83
|
+
|
|
84
|
+
# Add behaviors (executed in order)
|
|
85
|
+
mediator.add_pipeline_behavior(LoggingBehavior().handle)
|
|
86
|
+
mediator.add_pipeline_behavior(TimingBehavior().handle)
|
|
87
|
+
mediator.add_pipeline_behavior(ValidationBehavior().handle)
|
|
88
|
+
|
|
89
|
+
# Behaviors run before handler
|
|
90
|
+
result = await mediator.send(request)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Custom Behavior
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
async def auth_behavior(request, handler):
|
|
97
|
+
"""Check authentication before handler."""
|
|
98
|
+
if not request.user_id:
|
|
99
|
+
raise ValueError("Unauthorized")
|
|
100
|
+
return None # Continue to handler
|
|
101
|
+
|
|
102
|
+
mediator.add_pipeline_behavior(auth_behavior)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### `Mediator`
|
|
108
|
+
|
|
109
|
+
Generic request/response dispatcher.
|
|
110
|
+
|
|
111
|
+
**Methods:**
|
|
112
|
+
|
|
113
|
+
#### `register_handler(request_type, handler)`
|
|
114
|
+
|
|
115
|
+
Register handler for request type.
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
- `request_type` (Type): Request class
|
|
119
|
+
- `handler` (Any): Handler instance with `handle()` method
|
|
120
|
+
|
|
121
|
+
#### `async send(request) -> TResult`
|
|
122
|
+
|
|
123
|
+
Dispatch request to handler.
|
|
124
|
+
|
|
125
|
+
**Parameters:**
|
|
126
|
+
- `request` (TRequest): Request to send
|
|
127
|
+
|
|
128
|
+
**Returns:**
|
|
129
|
+
- Result from handler
|
|
130
|
+
|
|
131
|
+
#### `add_pipeline_behavior(behavior)`
|
|
132
|
+
|
|
133
|
+
Add pipeline behavior.
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
- `behavior` (Callable): Async function(request, handler) -> Optional[result]
|
|
137
|
+
|
|
138
|
+
### Built-in Behaviors
|
|
139
|
+
|
|
140
|
+
#### `LoggingBehavior`
|
|
141
|
+
|
|
142
|
+
Logs request type before/after handler.
|
|
143
|
+
|
|
144
|
+
**Requires:** `structlog>=23.0.0`
|
|
145
|
+
|
|
146
|
+
#### `TimingBehavior`
|
|
147
|
+
|
|
148
|
+
Measures and logs execution duration.
|
|
149
|
+
|
|
150
|
+
**Requires:** `structlog>=23.0.0`
|
|
151
|
+
|
|
152
|
+
#### `ValidationBehavior`
|
|
153
|
+
|
|
154
|
+
Validates Pydantic models before handler.
|
|
155
|
+
|
|
156
|
+
## Design Patterns
|
|
157
|
+
|
|
158
|
+
### Mediator Pattern
|
|
159
|
+
|
|
160
|
+
Decouples senders from receivers:
|
|
161
|
+
- Single point of dispatch
|
|
162
|
+
- Handler registration
|
|
163
|
+
- Pipeline behaviors
|
|
164
|
+
|
|
165
|
+
### Pipeline Pattern
|
|
166
|
+
|
|
167
|
+
Behaviors execute in order:
|
|
168
|
+
1. Logging
|
|
169
|
+
2. Timing
|
|
170
|
+
3. Validation
|
|
171
|
+
4. Handler
|
|
172
|
+
|
|
173
|
+
## Dependencies
|
|
174
|
+
|
|
175
|
+
- **Core**: None (zero dependencies!)
|
|
176
|
+
- **Optional**: `structlog>=23.0.0` (for logging behaviors)
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gridflow-python-mediator"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Generic mediator pattern with pipeline behaviors"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = {text = "MIT"}
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "GridFlow Team", email = "vialogue@proton.me"}
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
dependencies = [] # Zero dependencies - pure Python!
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/firstunicorn/python-web-toolkit"
|
|
16
|
+
Repository = "https://github.com/firstunicorn/python-web-toolkit"
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
logging = ["structlog>=23.0.0"] # For logging behaviors
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=7.4.0",
|
|
22
|
+
"pytest-asyncio>=0.21.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["setuptools>=68.0.0", "wheel"]
|
|
27
|
+
build-backend = "setuptools.build_meta"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
asyncio_mode = "auto"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest (>=9.0.2,<10.0.0)",
|
|
39
|
+
"pytest-asyncio (>=0.21.0)",
|
|
40
|
+
"hypothesis (>=6.151.5,<7.0.0)"
|
|
41
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Python Mediator - Generic mediator pattern implementation."""
|
|
2
|
+
|
|
3
|
+
from gridflow_python_mediator.mediator import Mediator
|
|
4
|
+
from gridflow_python_mediator.behaviors import (
|
|
5
|
+
PipelineBehavior,
|
|
6
|
+
LoggingBehavior,
|
|
7
|
+
TimingBehavior,
|
|
8
|
+
ValidationBehavior
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Mediator",
|
|
15
|
+
"PipelineBehavior",
|
|
16
|
+
"LoggingBehavior",
|
|
17
|
+
"TimingBehavior",
|
|
18
|
+
"ValidationBehavior",
|
|
19
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Pipeline behaviors for cross-cutting concerns."""
|
|
2
|
+
|
|
3
|
+
from gridflow_python_mediator.behaviors.protocol import PipelineBehavior
|
|
4
|
+
from gridflow_python_mediator.behaviors.observability import (
|
|
5
|
+
LoggingBehavior,
|
|
6
|
+
TimingBehavior,
|
|
7
|
+
)
|
|
8
|
+
from gridflow_python_mediator.behaviors.validation_behavior import (
|
|
9
|
+
ValidationBehavior,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"PipelineBehavior",
|
|
14
|
+
"LoggingBehavior",
|
|
15
|
+
"TimingBehavior",
|
|
16
|
+
"ValidationBehavior",
|
|
17
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Logging and timing pipeline behaviors."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import structlog
|
|
8
|
+
logger = structlog.get_logger()
|
|
9
|
+
except ImportError:
|
|
10
|
+
import logging
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LoggingBehavior:
|
|
15
|
+
"""Pipeline behavior for logging requests.
|
|
16
|
+
|
|
17
|
+
Logs request type before and after handler execution.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> mediator.add_pipeline_behavior(
|
|
21
|
+
... LoggingBehavior().handle
|
|
22
|
+
... )
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
async def handle(
|
|
26
|
+
self,
|
|
27
|
+
request: Any,
|
|
28
|
+
next: Callable
|
|
29
|
+
) -> Optional[Any]:
|
|
30
|
+
"""Log request handling.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
request: Request being handled
|
|
34
|
+
next: Next handler
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
None (continues to handler)
|
|
38
|
+
"""
|
|
39
|
+
request_type = type(request).__name__
|
|
40
|
+
logger.info("Handling request", request_type=request_type)
|
|
41
|
+
|
|
42
|
+
result = await next(request)
|
|
43
|
+
|
|
44
|
+
logger.info("Request completed", request_type=request_type)
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TimingBehavior:
|
|
49
|
+
"""Pipeline behavior for timing request execution.
|
|
50
|
+
|
|
51
|
+
Measures and logs execution duration.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> mediator.add_pipeline_behavior(
|
|
55
|
+
... TimingBehavior().handle
|
|
56
|
+
... )
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
async def handle(
|
|
60
|
+
self,
|
|
61
|
+
request: Any,
|
|
62
|
+
next: Callable
|
|
63
|
+
) -> Optional[Any]:
|
|
64
|
+
"""Time request handling.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
request: Request being handled
|
|
68
|
+
next: Next handler
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Handler result
|
|
72
|
+
"""
|
|
73
|
+
start = time.time()
|
|
74
|
+
request_type = type(request).__name__
|
|
75
|
+
|
|
76
|
+
result = await next(request)
|
|
77
|
+
|
|
78
|
+
duration_ms = int((time.time() - start) * 1000)
|
|
79
|
+
logger.info(
|
|
80
|
+
"Request timing",
|
|
81
|
+
request_type=request_type,
|
|
82
|
+
duration_ms=duration_ms
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return result
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""PipelineBehavior protocol definition."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Protocol, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PipelineBehavior(Protocol):
|
|
7
|
+
"""Protocol for pipeline behaviors.
|
|
8
|
+
|
|
9
|
+
Behaviors execute before handler and can:
|
|
10
|
+
- Log/monitor requests
|
|
11
|
+
- Validate requests
|
|
12
|
+
- Transform requests
|
|
13
|
+
- Short-circuit execution
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> class CustomBehavior:
|
|
17
|
+
... async def handle(self, request, next):
|
|
18
|
+
... # Custom logic
|
|
19
|
+
... return await next(request)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
async def handle(
|
|
23
|
+
self,
|
|
24
|
+
request: Any,
|
|
25
|
+
next: Callable
|
|
26
|
+
) -> Optional[Any]:
|
|
27
|
+
"""Handle request in pipeline.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
request: Request to handle
|
|
31
|
+
next: Next handler in pipeline
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Result (or None to continue pipeline)
|
|
35
|
+
"""
|
|
36
|
+
...
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Validation pipeline behavior for Pydantic models."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ValidationBehavior:
|
|
7
|
+
"""Pipeline behavior for request validation.
|
|
8
|
+
|
|
9
|
+
Validates Pydantic models before handler execution.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> mediator.add_pipeline_behavior(
|
|
13
|
+
... ValidationBehavior().handle
|
|
14
|
+
... )
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
async def handle(
|
|
18
|
+
self,
|
|
19
|
+
request: Any,
|
|
20
|
+
next: Callable
|
|
21
|
+
) -> Optional[Any]:
|
|
22
|
+
"""Validate request.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
request: Request to validate
|
|
26
|
+
next: Next handler
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Handler result
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValidationError: If validation fails
|
|
33
|
+
"""
|
|
34
|
+
# Validate if Pydantic model
|
|
35
|
+
if hasattr(request, 'model_validate'):
|
|
36
|
+
request.model_validate(request)
|
|
37
|
+
|
|
38
|
+
return await next(request)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Core mediator for request/response dispatch.
|
|
2
|
+
|
|
3
|
+
Extracted from GridFlow backend/src/apps/token_generator/application/common/mediator/mediator.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import TypeVar, Type, Dict, Any, Callable, List
|
|
7
|
+
import inspect
|
|
8
|
+
|
|
9
|
+
TRequest = TypeVar('TRequest')
|
|
10
|
+
TResult = TypeVar('TResult')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Mediator:
|
|
14
|
+
"""Generic request/response dispatcher (Mediator pattern).
|
|
15
|
+
|
|
16
|
+
Decouples senders from handlers, allowing for:
|
|
17
|
+
- Pipeline behaviors (logging, timing, validation)
|
|
18
|
+
- Dynamic handler registration
|
|
19
|
+
- Type-safe dispatch
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
>>> mediator = Mediator()
|
|
23
|
+
>>> mediator.register_handler(MyRequest, MyHandler())
|
|
24
|
+
>>> result = await mediator.send(MyRequest(...))
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
"""Initialize mediator with empty handler registry."""
|
|
29
|
+
self._handlers: Dict[Type, Any] = {}
|
|
30
|
+
self._behaviors: List[Callable] = []
|
|
31
|
+
|
|
32
|
+
def register_handler(
|
|
33
|
+
self,
|
|
34
|
+
request_type: Type[TRequest],
|
|
35
|
+
handler: Any
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Register handler for request type.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
request_type: Request/Command/Query type
|
|
41
|
+
handler: Handler instance with handle() method
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
ValueError: If handler already registered for type
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> mediator.register_handler(CreateUserCommand, CreateUserHandler())
|
|
48
|
+
"""
|
|
49
|
+
if request_type in self._handlers:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Handler already registered for {request_type.__name__}"
|
|
52
|
+
)
|
|
53
|
+
self._handlers[request_type] = handler
|
|
54
|
+
|
|
55
|
+
async def send(self, request: TRequest) -> TResult:
|
|
56
|
+
"""Dispatch request to its registered handler.
|
|
57
|
+
|
|
58
|
+
Executes pipeline behaviors before calling handler.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
request: Request/Command/Query to send
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Result from handler
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
ValueError: If no handler registered for request type
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> cmd = CreateUserCommand(name="John")
|
|
71
|
+
>>> user_id = await mediator.send(cmd)
|
|
72
|
+
"""
|
|
73
|
+
request_type = type(request)
|
|
74
|
+
handler = self._handlers.get(request_type)
|
|
75
|
+
|
|
76
|
+
if not handler:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"No handler registered for {request_type.__name__}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Execute pipeline behaviors
|
|
82
|
+
for behavior in self._behaviors:
|
|
83
|
+
# Behaviors can modify request or short-circuit
|
|
84
|
+
result = await behavior(request, handler)
|
|
85
|
+
if result is not None:
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
# Execute handler
|
|
89
|
+
if inspect.iscoroutinefunction(handler.handle):
|
|
90
|
+
return await handler.handle(request)
|
|
91
|
+
else:
|
|
92
|
+
return handler.handle(request)
|
|
93
|
+
|
|
94
|
+
def add_pipeline_behavior(self, behavior: Callable) -> None:
|
|
95
|
+
"""Add pipeline behavior for cross-cutting concerns.
|
|
96
|
+
|
|
97
|
+
Behaviors are executed in order before handler.
|
|
98
|
+
Can implement logging, timing, validation, etc.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
behavior: Async function(request, handler) -> Optional[result]
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> async def logging_behavior(request, handler):
|
|
105
|
+
... print(f"Handling {type(request).__name__}")
|
|
106
|
+
... return None # Continue to handler
|
|
107
|
+
>>>
|
|
108
|
+
>>> mediator.add_pipeline_behavior(logging_behavior)
|
|
109
|
+
"""
|
|
110
|
+
self._behaviors.append(behavior)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gridflow-python-mediator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generic mediator pattern with pipeline behaviors
|
|
5
|
+
Author-email: GridFlow Team <vialogue@proton.me>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/firstunicorn/python-web-toolkit
|
|
8
|
+
Project-URL: Repository, https://github.com/firstunicorn/python-web-toolkit
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Provides-Extra: logging
|
|
12
|
+
Requires-Dist: structlog>=23.0.0; extra == "logging"
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# gridflow-python-mediator
|
|
18
|
+
|
|
19
|
+
Generic mediator pattern implementation with zero dependencies and pipeline behavior support.
|
|
20
|
+
|
|
21
|
+
**Extracted from:** GridFlow `backend/src/apps/token_generator/application/common/mediator/`
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Core (zero dependencies)
|
|
27
|
+
pip install gridflow-python-mediator
|
|
28
|
+
|
|
29
|
+
# With logging support
|
|
30
|
+
pip install gridflow-python-mediator[logging]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Zero Dependencies**: Pure Python implementation
|
|
36
|
+
- **Pipeline Behaviors**: Logging, timing, validation
|
|
37
|
+
- **Type-Safe**: Full generic type support
|
|
38
|
+
- **Async/Sync**: Supports both async and sync handlers
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Basic Mediator
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from gridflow_python_mediator import Mediator
|
|
46
|
+
|
|
47
|
+
# Define request and handler
|
|
48
|
+
class GetUserRequest:
|
|
49
|
+
def __init__(self, user_id: int):
|
|
50
|
+
self.user_id = user_id
|
|
51
|
+
|
|
52
|
+
class GetUserHandler:
|
|
53
|
+
async def handle(self, request: GetUserRequest):
|
|
54
|
+
# Fetch user logic
|
|
55
|
+
return {"id": request.user_id, "name": "John"}
|
|
56
|
+
|
|
57
|
+
# Setup mediator
|
|
58
|
+
mediator = Mediator()
|
|
59
|
+
mediator.register_handler(GetUserRequest, GetUserHandler())
|
|
60
|
+
|
|
61
|
+
# Send request
|
|
62
|
+
request = GetUserRequest(user_id=1)
|
|
63
|
+
result = await mediator.send(request)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### With CQRS
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from python_cqrs_core import BaseCommand, ICommandHandler
|
|
70
|
+
from gridflow_python_mediator import Mediator
|
|
71
|
+
|
|
72
|
+
class CreateUserCommand(BaseCommand):
|
|
73
|
+
name: str
|
|
74
|
+
email: str
|
|
75
|
+
|
|
76
|
+
class CreateUserHandler(ICommandHandler[CreateUserCommand, int]):
|
|
77
|
+
async def handle(self, command: CreateUserCommand) -> int:
|
|
78
|
+
# Create user
|
|
79
|
+
return user_id
|
|
80
|
+
|
|
81
|
+
mediator = Mediator()
|
|
82
|
+
mediator.register_handler(CreateUserCommand, CreateUserHandler())
|
|
83
|
+
|
|
84
|
+
cmd = CreateUserCommand(name="John", email="john@example.com")
|
|
85
|
+
user_id = await mediator.send(cmd)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Pipeline Behaviors
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from gridflow_python_mediator import (
|
|
92
|
+
Mediator,
|
|
93
|
+
LoggingBehavior,
|
|
94
|
+
TimingBehavior,
|
|
95
|
+
ValidationBehavior
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
mediator = Mediator()
|
|
99
|
+
|
|
100
|
+
# Add behaviors (executed in order)
|
|
101
|
+
mediator.add_pipeline_behavior(LoggingBehavior().handle)
|
|
102
|
+
mediator.add_pipeline_behavior(TimingBehavior().handle)
|
|
103
|
+
mediator.add_pipeline_behavior(ValidationBehavior().handle)
|
|
104
|
+
|
|
105
|
+
# Behaviors run before handler
|
|
106
|
+
result = await mediator.send(request)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Custom Behavior
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
async def auth_behavior(request, handler):
|
|
113
|
+
"""Check authentication before handler."""
|
|
114
|
+
if not request.user_id:
|
|
115
|
+
raise ValueError("Unauthorized")
|
|
116
|
+
return None # Continue to handler
|
|
117
|
+
|
|
118
|
+
mediator.add_pipeline_behavior(auth_behavior)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API Reference
|
|
122
|
+
|
|
123
|
+
### `Mediator`
|
|
124
|
+
|
|
125
|
+
Generic request/response dispatcher.
|
|
126
|
+
|
|
127
|
+
**Methods:**
|
|
128
|
+
|
|
129
|
+
#### `register_handler(request_type, handler)`
|
|
130
|
+
|
|
131
|
+
Register handler for request type.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
- `request_type` (Type): Request class
|
|
135
|
+
- `handler` (Any): Handler instance with `handle()` method
|
|
136
|
+
|
|
137
|
+
#### `async send(request) -> TResult`
|
|
138
|
+
|
|
139
|
+
Dispatch request to handler.
|
|
140
|
+
|
|
141
|
+
**Parameters:**
|
|
142
|
+
- `request` (TRequest): Request to send
|
|
143
|
+
|
|
144
|
+
**Returns:**
|
|
145
|
+
- Result from handler
|
|
146
|
+
|
|
147
|
+
#### `add_pipeline_behavior(behavior)`
|
|
148
|
+
|
|
149
|
+
Add pipeline behavior.
|
|
150
|
+
|
|
151
|
+
**Parameters:**
|
|
152
|
+
- `behavior` (Callable): Async function(request, handler) -> Optional[result]
|
|
153
|
+
|
|
154
|
+
### Built-in Behaviors
|
|
155
|
+
|
|
156
|
+
#### `LoggingBehavior`
|
|
157
|
+
|
|
158
|
+
Logs request type before/after handler.
|
|
159
|
+
|
|
160
|
+
**Requires:** `structlog>=23.0.0`
|
|
161
|
+
|
|
162
|
+
#### `TimingBehavior`
|
|
163
|
+
|
|
164
|
+
Measures and logs execution duration.
|
|
165
|
+
|
|
166
|
+
**Requires:** `structlog>=23.0.0`
|
|
167
|
+
|
|
168
|
+
#### `ValidationBehavior`
|
|
169
|
+
|
|
170
|
+
Validates Pydantic models before handler.
|
|
171
|
+
|
|
172
|
+
## Design Patterns
|
|
173
|
+
|
|
174
|
+
### Mediator Pattern
|
|
175
|
+
|
|
176
|
+
Decouples senders from receivers:
|
|
177
|
+
- Single point of dispatch
|
|
178
|
+
- Handler registration
|
|
179
|
+
- Pipeline behaviors
|
|
180
|
+
|
|
181
|
+
### Pipeline Pattern
|
|
182
|
+
|
|
183
|
+
Behaviors execute in order:
|
|
184
|
+
1. Logging
|
|
185
|
+
2. Timing
|
|
186
|
+
3. Validation
|
|
187
|
+
4. Handler
|
|
188
|
+
|
|
189
|
+
## Dependencies
|
|
190
|
+
|
|
191
|
+
- **Core**: None (zero dependencies!)
|
|
192
|
+
- **Optional**: `structlog>=23.0.0` (for logging behaviors)
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/gridflow_python_mediator/__init__.py
|
|
4
|
+
src/gridflow_python_mediator/mediator.py
|
|
5
|
+
src/gridflow_python_mediator.egg-info/PKG-INFO
|
|
6
|
+
src/gridflow_python_mediator.egg-info/SOURCES.txt
|
|
7
|
+
src/gridflow_python_mediator.egg-info/dependency_links.txt
|
|
8
|
+
src/gridflow_python_mediator.egg-info/requires.txt
|
|
9
|
+
src/gridflow_python_mediator.egg-info/top_level.txt
|
|
10
|
+
src/gridflow_python_mediator/behaviors/__init__.py
|
|
11
|
+
src/gridflow_python_mediator/behaviors/observability.py
|
|
12
|
+
src/gridflow_python_mediator/behaviors/protocol.py
|
|
13
|
+
src/gridflow_python_mediator/behaviors/validation_behavior.py
|
|
14
|
+
tests/test_mediator.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gridflow_python_mediator
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Unit tests for Mediator class.
|
|
2
|
+
|
|
3
|
+
Tests core mediator functionality including handler registration and dispatch.
|
|
4
|
+
RULE: Maximum 100 lines per file.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from gridflow_python_mediator import Mediator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SampleRequest:
|
|
12
|
+
"""Sample request for testing."""
|
|
13
|
+
def __init__(self, value: str):
|
|
14
|
+
self.value = value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SampleHandler:
|
|
18
|
+
"""Sample handler for testing."""
|
|
19
|
+
async def handle(self, request: SampleRequest) -> str:
|
|
20
|
+
return f"Handled: {request.value}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AnotherRequest:
|
|
24
|
+
"""Another request for testing."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_mediator_initialization():
|
|
29
|
+
"""Should initialize with empty handlers and behaviors."""
|
|
30
|
+
mediator = Mediator()
|
|
31
|
+
|
|
32
|
+
assert mediator._handlers == {}
|
|
33
|
+
assert mediator._behaviors == []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_register_handler():
|
|
37
|
+
"""Should register handler for request type."""
|
|
38
|
+
mediator = Mediator()
|
|
39
|
+
handler = SampleHandler()
|
|
40
|
+
|
|
41
|
+
mediator.register_handler(SampleRequest, handler)
|
|
42
|
+
|
|
43
|
+
assert SampleRequest in mediator._handlers
|
|
44
|
+
assert mediator._handlers[SampleRequest] is handler
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_register_duplicate_handler_raises():
|
|
48
|
+
"""Should raise on duplicate handler registration."""
|
|
49
|
+
mediator = Mediator()
|
|
50
|
+
handler = SampleHandler()
|
|
51
|
+
|
|
52
|
+
mediator.register_handler(SampleRequest, handler)
|
|
53
|
+
|
|
54
|
+
with pytest.raises(ValueError, match="Handler already registered"):
|
|
55
|
+
mediator.register_handler(SampleRequest, handler)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def test_send_dispatches_to_handler():
|
|
59
|
+
"""Should dispatch request to registered handler."""
|
|
60
|
+
mediator = Mediator()
|
|
61
|
+
mediator.register_handler(SampleRequest, SampleHandler())
|
|
62
|
+
|
|
63
|
+
result = await mediator.send(SampleRequest("test"))
|
|
64
|
+
|
|
65
|
+
assert result == "Handled: test"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def test_send_unregistered_raises():
|
|
69
|
+
"""Should raise on unregistered request type."""
|
|
70
|
+
mediator = Mediator()
|
|
71
|
+
|
|
72
|
+
with pytest.raises(ValueError, match="No handler registered"):
|
|
73
|
+
await mediator.send(AnotherRequest())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def test_add_pipeline_behavior():
|
|
77
|
+
"""Should execute pipeline behaviors before handler."""
|
|
78
|
+
mediator = Mediator()
|
|
79
|
+
mediator.register_handler(SampleRequest, SampleHandler())
|
|
80
|
+
|
|
81
|
+
called = []
|
|
82
|
+
|
|
83
|
+
async def logging_behavior(request, handler):
|
|
84
|
+
called.append("behavior")
|
|
85
|
+
return None # Continue to handler
|
|
86
|
+
|
|
87
|
+
mediator.add_pipeline_behavior(logging_behavior)
|
|
88
|
+
result = await mediator.send(SampleRequest("test"))
|
|
89
|
+
|
|
90
|
+
assert "behavior" in called
|
|
91
|
+
assert result == "Handled: test"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def test_pipeline_behavior_short_circuit():
|
|
95
|
+
"""Should allow behavior to short-circuit handler."""
|
|
96
|
+
mediator = Mediator()
|
|
97
|
+
mediator.register_handler(SampleRequest, SampleHandler())
|
|
98
|
+
|
|
99
|
+
async def short_circuit_behavior(request, handler):
|
|
100
|
+
return "Short-circuited"
|
|
101
|
+
|
|
102
|
+
mediator.add_pipeline_behavior(short_circuit_behavior)
|
|
103
|
+
result = await mediator.send(SampleRequest("test"))
|
|
104
|
+
|
|
105
|
+
assert result == "Short-circuited"
|