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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,7 @@
1
+
2
+ [dev]
3
+ pytest>=7.4.0
4
+ pytest-asyncio>=0.21.0
5
+
6
+ [logging]
7
+ structlog>=23.0.0
@@ -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"