python-cq 0.14.0__tar.gz → 0.14.1__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.
Files changed (28) hide show
  1. {python_cq-0.14.0 → python_cq-0.14.1}/PKG-INFO +1 -1
  2. python_cq-0.14.1/cq/_core/common/typing.py +21 -0
  3. python_cq-0.14.1/cq/_core/dispatcher/pipe.py +312 -0
  4. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/handler.py +4 -6
  5. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/message.py +1 -1
  6. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/middleware.py +1 -1
  7. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/pipetools.py +17 -8
  8. python_cq-0.14.1/cq/py.typed +0 -0
  9. {python_cq-0.14.0 → python_cq-0.14.1}/pyproject.toml +1 -1
  10. python_cq-0.14.0/cq/_core/dispatcher/pipe.py +0 -210
  11. {python_cq-0.14.0 → python_cq-0.14.1}/.gitignore +0 -0
  12. {python_cq-0.14.0 → python_cq-0.14.1}/LICENSE +0 -0
  13. {python_cq-0.14.0 → python_cq-0.14.1}/cq/__init__.py +0 -0
  14. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/__init__.py +0 -0
  15. {python_cq-0.14.0/cq/_core/dispatcher → python_cq-0.14.1/cq/_core/common}/__init__.py +0 -0
  16. {python_cq-0.14.0/cq/ext → python_cq-0.14.1/cq/_core/dispatcher}/__init__.py +0 -0
  17. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/dispatcher/base.py +0 -0
  18. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/dispatcher/bus.py +0 -0
  19. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/dispatcher/lazy.py +0 -0
  20. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/related_events.py +0 -0
  21. {python_cq-0.14.0 → python_cq-0.14.1}/cq/_core/scope.py +0 -0
  22. {python_cq-0.14.0 → python_cq-0.14.1}/cq/exceptions.py +0 -0
  23. {python_cq-0.14.0/cq/middlewares → python_cq-0.14.1/cq/ext}/__init__.py +0 -0
  24. {python_cq-0.14.0 → python_cq-0.14.1}/cq/ext/fastapi.py +0 -0
  25. /python_cq-0.14.0/cq/py.typed → /python_cq-0.14.1/cq/middlewares/__init__.py +0 -0
  26. {python_cq-0.14.0 → python_cq-0.14.1}/cq/middlewares/retry.py +0 -0
  27. {python_cq-0.14.0 → python_cq-0.14.1}/cq/middlewares/scope.py +0 -0
  28. {python_cq-0.14.0 → python_cq-0.14.1}/docs/index.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cq
3
- Version: 0.14.0
3
+ Version: 0.14.1
4
4
  Summary: CQRS library for async Python projects.
5
5
  Project-URL: Documentation, https://python-cq.remimd.dev
6
6
  Project-URL: Repository, https://github.com/100nm/python-cq
@@ -0,0 +1,21 @@
1
+ from collections.abc import Callable
2
+ from typing import Any, Protocol, overload
3
+
4
+
5
+ class Decorator(Protocol):
6
+ def __call__[T](self, wrapped: T, /) -> T: ...
7
+
8
+
9
+ class Method[**P, T](Protocol):
10
+ @overload
11
+ def __call__(self, instance: Any, /, *args: P.args, **kwargs: P.kwargs) -> T: ...
12
+
13
+ @overload
14
+ def __call__(self, /, *args: Any, **kwargs: Any) -> T: ...
15
+
16
+ def __get__(
17
+ self,
18
+ instance: object,
19
+ owner: type | None = ...,
20
+ /,
21
+ ) -> Callable[P, T]: ...
@@ -0,0 +1,312 @@
1
+ from abc import abstractmethod
2
+ from collections.abc import Awaitable, Callable
3
+ from dataclasses import dataclass, field
4
+ from functools import partial
5
+ from inspect import iscoroutinefunction
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ Any,
9
+ Concatenate,
10
+ Protocol,
11
+ Self,
12
+ overload,
13
+ runtime_checkable,
14
+ )
15
+
16
+ from cq._core.common.typing import Decorator, Method
17
+ from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
18
+ from cq._core.middleware import Middleware, MiddlewareGroup
19
+
20
+ type ConvertAsync[**P, I, O] = Callable[Concatenate[O, P], Awaitable[I]]
21
+ type ConvertSync[**P, I, O] = Callable[Concatenate[O, P], I]
22
+ type Convert[**P, I, O] = ConvertAsync[P, I, O] | ConvertSync[P, I, O]
23
+
24
+ type ConvertMethodAsync[I, O] = Method[[O], Awaitable[I]]
25
+ type ConvertMethodSync[I, O] = Method[[O], I]
26
+ type ConvertMethod[I, O] = ConvertMethodAsync[I, O] | ConvertMethodSync[I, O]
27
+
28
+
29
+ @runtime_checkable
30
+ class PipelineConverter[**P, I, O](Protocol):
31
+ __slots__ = ()
32
+
33
+ @abstractmethod
34
+ async def convert(self, output_value: O, /, *args: P.args, **kwargs: P.kwargs) -> I:
35
+ raise NotImplementedError
36
+
37
+
38
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
39
+ class PipelineStep[**P, I, O]:
40
+ converter: PipelineConverter[P, I, O]
41
+ dispatcher: Dispatcher[I, Any] | None = field(default=None)
42
+
43
+
44
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
45
+ class PipelineSteps[**P, I, O]:
46
+ default_dispatcher: Dispatcher[Any, Any]
47
+ __steps: list[PipelineStep[P, Any, Any]] = field(default_factory=list, init=False)
48
+
49
+ def add[T](
50
+ self,
51
+ converter: PipelineConverter[P, T, Any],
52
+ dispatcher: Dispatcher[T, Any] | None,
53
+ ) -> Self:
54
+ self.__steps.append(PipelineStep(converter, dispatcher))
55
+ return self
56
+
57
+ async def execute(self, input_value: I, /, *args: P.args, **kwargs: P.kwargs) -> O:
58
+ dispatcher = self.default_dispatcher
59
+
60
+ for step in self.__steps:
61
+ output_value = await dispatcher.dispatch(input_value)
62
+ input_value = await step.converter.convert(output_value, *args, **kwargs)
63
+
64
+ if input_value is None:
65
+ return NotImplemented
66
+
67
+ dispatcher = step.dispatcher or self.default_dispatcher
68
+
69
+ return await dispatcher.dispatch(input_value)
70
+
71
+
72
+ class Pipe[I, O](BaseDispatcher[I, O]):
73
+ __slots__ = ("__steps",)
74
+
75
+ __steps: PipelineSteps[[], I, O]
76
+
77
+ def __init__(self, dispatcher: Dispatcher[Any, Any]) -> None:
78
+ super().__init__()
79
+ self.__steps = PipelineSteps(dispatcher)
80
+
81
+ if TYPE_CHECKING: # pragma: no cover
82
+
83
+ @overload
84
+ def step[T](
85
+ self,
86
+ wrapped: ConvertAsync[[], T, Any],
87
+ /,
88
+ *,
89
+ dispatcher: Dispatcher[T, Any] | None = ...,
90
+ ) -> ConvertAsync[[], T, Any]: ...
91
+
92
+ @overload
93
+ def step[T](
94
+ self,
95
+ wrapped: ConvertSync[[], T, Any],
96
+ /,
97
+ *,
98
+ dispatcher: Dispatcher[T, Any] | None = ...,
99
+ ) -> ConvertSync[[], T, Any]: ...
100
+
101
+ @overload
102
+ def step(
103
+ self,
104
+ wrapped: None = ...,
105
+ /,
106
+ *,
107
+ dispatcher: Dispatcher[Any, Any] | None = ...,
108
+ ) -> Decorator: ...
109
+
110
+ def step[T](
111
+ self,
112
+ wrapped: Convert[[], T, Any] | None = None,
113
+ /,
114
+ *,
115
+ dispatcher: Dispatcher[T, Any] | None = None,
116
+ ) -> Any:
117
+ def decorator(wp: Convert[[], T, Any]) -> Convert[[], T, Any]:
118
+ converter = (
119
+ _AsyncPipelineConverter(wp)
120
+ if iscoroutinefunction(wp)
121
+ else _SyncPipelineConverter(wp)
122
+ )
123
+ self.__steps.add(converter, dispatcher)
124
+ return wp
125
+
126
+ return decorator(wrapped) if wrapped else decorator
127
+
128
+ def add_static_step[T](
129
+ self,
130
+ input_value: T,
131
+ *,
132
+ dispatcher: Dispatcher[T, Any] | None = None,
133
+ ) -> Self:
134
+ converter = _StaticPipelineConverter(input_value)
135
+ self.__steps.add(converter, dispatcher)
136
+ return self
137
+
138
+ async def dispatch(self, input_value: I, /) -> O:
139
+ return await self._invoke_with_middlewares(self.__steps.execute, input_value)
140
+
141
+
142
+ class ContextPipeline[I]:
143
+ __slots__ = ("__middleware_group", "__steps")
144
+
145
+ __middleware_group: MiddlewareGroup[[I], Any]
146
+ __steps: PipelineSteps[[object, type | None], I, Any]
147
+
148
+ def __init__(self, dispatcher: Dispatcher[Any, Any]) -> None:
149
+ self.__middleware_group = MiddlewareGroup()
150
+ self.__steps = PipelineSteps(dispatcher)
151
+
152
+ if TYPE_CHECKING: # pragma: no cover
153
+
154
+ @overload
155
+ def __get__[O](self, instance: None, owner: type[O], /) -> Dispatcher[I, O]: ...
156
+
157
+ @overload
158
+ def __get__[O](
159
+ self,
160
+ instance: O,
161
+ owner: type[O] | None = ...,
162
+ /,
163
+ ) -> Dispatcher[I, O]: ...
164
+
165
+ @overload
166
+ def __get__(self, instance: None = ..., owner: None = ..., /) -> Self: ...
167
+
168
+ def __get__[O](
169
+ self,
170
+ instance: O | None = None,
171
+ owner: type[O] | None = None,
172
+ /,
173
+ ) -> Self | Dispatcher[I, O]:
174
+ if instance is None:
175
+ if owner is None:
176
+ return self
177
+
178
+ instance = owner()
179
+
180
+ dispatch_method = partial(self.__execute, context=instance, context_type=owner)
181
+ return BoundContextPipeline(dispatch_method)
182
+
183
+ def add_middlewares(self, *middlewares: Middleware[[I], Any]) -> Self:
184
+ self.__middleware_group.add(*middlewares)
185
+ return self
186
+
187
+ if TYPE_CHECKING: # pragma: no cover
188
+
189
+ @overload
190
+ def step[T](
191
+ self,
192
+ wrapped: ConvertMethodAsync[T, Any],
193
+ /,
194
+ *,
195
+ dispatcher: Dispatcher[T, Any] | None = ...,
196
+ ) -> ConvertMethodAsync[T, Any]: ...
197
+
198
+ @overload
199
+ def step[T](
200
+ self,
201
+ wrapped: ConvertMethodSync[T, Any],
202
+ /,
203
+ *,
204
+ dispatcher: Dispatcher[T, Any] | None = ...,
205
+ ) -> ConvertMethodSync[T, Any]: ...
206
+
207
+ @overload
208
+ def step(
209
+ self,
210
+ wrapped: None = ...,
211
+ /,
212
+ *,
213
+ dispatcher: Dispatcher[Any, Any] | None = ...,
214
+ ) -> Decorator: ...
215
+
216
+ def step[T](
217
+ self,
218
+ wrapped: ConvertMethod[T, Any] | None = None,
219
+ /,
220
+ *,
221
+ dispatcher: Dispatcher[T, Any] | None = None,
222
+ ) -> Any:
223
+ def decorator(wp: ConvertMethod[T, Any]) -> ConvertMethod[T, Any]:
224
+ converter = (
225
+ _AsyncContextPipelineConverter(wp)
226
+ if iscoroutinefunction(wp)
227
+ else _SyncContextPipelineConverter(wp)
228
+ )
229
+ self.__steps.add(converter, dispatcher)
230
+ return wp
231
+
232
+ return decorator(wrapped) if wrapped else decorator
233
+
234
+ async def __execute[O](
235
+ self,
236
+ input_value: I,
237
+ /,
238
+ *,
239
+ context: O,
240
+ context_type: type[O] | None,
241
+ ) -> O:
242
+ await self.__middleware_group.invoke(
243
+ lambda i: self.__steps.execute(i, context, context_type),
244
+ input_value,
245
+ )
246
+ return context
247
+
248
+
249
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
250
+ class BoundContextPipeline[I, O](Dispatcher[I, O]):
251
+ dispatch_method: Callable[[I], Awaitable[O]]
252
+
253
+ async def dispatch(self, input_value: I, /) -> O:
254
+ return await self.dispatch_method(input_value)
255
+
256
+
257
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
258
+ class _AsyncPipelineConverter[**P, I, O](PipelineConverter[P, I, O]):
259
+ converter: ConvertAsync[P, I, O]
260
+
261
+ async def convert(self, output_value: O, /, *args: P.args, **kwargs: P.kwargs) -> I:
262
+ return await self.converter(output_value, *args, **kwargs)
263
+
264
+
265
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
266
+ class _SyncPipelineConverter[**P, I, O](PipelineConverter[P, I, O]):
267
+ converter: ConvertSync[P, I, O]
268
+
269
+ async def convert(self, output_value: O, /, *args: P.args, **kwargs: P.kwargs) -> I:
270
+ return self.converter(output_value, *args, **kwargs)
271
+
272
+
273
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
274
+ class _StaticPipelineConverter[I](PipelineConverter[..., I, Any]):
275
+ input_value: I
276
+
277
+ async def convert(self, output_value: Any, /, *args: Any, **kwargs: Any) -> I:
278
+ return self.input_value
279
+
280
+
281
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
282
+ class _AsyncContextPipelineConverter[I, O](
283
+ PipelineConverter[[object, type | None], I, O],
284
+ ):
285
+ converter: ConvertMethodAsync[I, O]
286
+
287
+ async def convert(
288
+ self,
289
+ output_value: O,
290
+ /,
291
+ context: object,
292
+ context_type: type | None,
293
+ ) -> I:
294
+ method = self.converter.__get__(context, context_type)
295
+ return await method(output_value)
296
+
297
+
298
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
299
+ class _SyncContextPipelineConverter[I, O](
300
+ PipelineConverter[[object, type | None], I, O],
301
+ ):
302
+ converter: ConvertMethodSync[I, O]
303
+
304
+ async def convert(
305
+ self,
306
+ output_value: O,
307
+ /,
308
+ context: object,
309
+ context_type: type | None,
310
+ ) -> I:
311
+ method = self.converter.__get__(context, context_type)
312
+ return method(output_value)
@@ -10,6 +10,8 @@ from typing import TYPE_CHECKING, Any, Protocol, Self, overload, runtime_checkab
10
10
  import injection
11
11
  from type_analyzer import MatchingTypesConfig, iter_matching_types, matching_types
12
12
 
13
+ from cq._core.common.typing import Decorator
14
+
13
15
  type HandlerType[**P, T] = type[Handler[P, T]]
14
16
  type HandlerFactory[**P, T] = Callable[..., Awaitable[Handler[P, T]]]
15
17
 
@@ -90,10 +92,6 @@ class SingleHandlerRegistry[I, O](HandlerRegistry[I, O]):
90
92
  return self
91
93
 
92
94
 
93
- class _Decorator(Protocol):
94
- def __call__[T](self, wrapped: T, /) -> T: ...
95
-
96
-
97
95
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
98
96
  class HandlerDecorator[I, O]:
99
97
  registry: HandlerRegistry[I, O]
@@ -108,7 +106,7 @@ class HandlerDecorator[I, O]:
108
106
  /,
109
107
  *,
110
108
  threadsafe: bool | None = ...,
111
- ) -> _Decorator: ...
109
+ ) -> Decorator: ...
112
110
 
113
111
  @overload
114
112
  def __call__[T](
@@ -126,7 +124,7 @@ class HandlerDecorator[I, O]:
126
124
  /,
127
125
  *,
128
126
  threadsafe: bool | None = ...,
129
- ) -> _Decorator: ...
127
+ ) -> Decorator: ...
130
128
 
131
129
  def __call__[T](
132
130
  self,
@@ -26,7 +26,7 @@ AnyCommandBus = CommandBus[Any]
26
26
  command_handler: Final[HandlerDecorator[Command, Any]] = HandlerDecorator(
27
27
  SingleHandlerRegistry(),
28
28
  )
29
- event_handler: Final[HandlerDecorator[Event, None]] = HandlerDecorator(
29
+ event_handler: Final[HandlerDecorator[Event, Any]] = HandlerDecorator(
30
30
  MultipleHandlerRegistry(),
31
31
  )
32
32
  query_handler: Final[HandlerDecorator[Query, Any]] = HandlerDecorator(
@@ -59,7 +59,7 @@ class _BoundMiddleware[**P, T]:
59
59
  call_next: Callable[P, Awaitable[T]]
60
60
  middleware: ClassicMiddleware[P, T]
61
61
 
62
- async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
62
+ async def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
63
63
  return await self.middleware(self.call_next, *args, **kwargs)
64
64
 
65
65
 
@@ -1,10 +1,16 @@
1
- from typing import TYPE_CHECKING, Any, Callable, overload
1
+ from typing import TYPE_CHECKING, Any, overload
2
2
 
3
3
  import injection
4
4
 
5
5
  from cq import Dispatcher
6
+ from cq._core.common.typing import Decorator
6
7
  from cq._core.dispatcher.lazy import LazyDispatcher
7
- from cq._core.dispatcher.pipe import ContextPipeline, PipeConverterMethod
8
+ from cq._core.dispatcher.pipe import (
9
+ ContextPipeline,
10
+ ConvertMethod,
11
+ ConvertMethodAsync,
12
+ ConvertMethodSync,
13
+ )
8
14
  from cq._core.message import AnyCommandBus, Command, Query, QueryBus
9
15
  from cq._core.scope import CQScope
10
16
  from cq.middlewares.scope import InjectionScopeMiddleware
@@ -47,20 +53,23 @@ class ContextCommandPipeline[I: Command](ContextPipeline[I]):
47
53
  @overload
48
54
  def query_step[T: Query](
49
55
  self,
50
- wrapped: PipeConverterMethod[T, Any],
56
+ wrapped: ConvertMethodAsync[T, Any],
51
57
  /,
52
- ) -> PipeConverterMethod[T, Any]: ...
58
+ ) -> ConvertMethodAsync[T, Any]: ...
53
59
 
54
60
  @overload
55
61
  def query_step[T: Query](
56
62
  self,
57
- wrapped: None = ...,
63
+ wrapped: ConvertMethodSync[T, Any],
58
64
  /,
59
- ) -> Callable[[PipeConverterMethod[T, Any]], PipeConverterMethod[T, Any]]: ...
65
+ ) -> ConvertMethodSync[T, Any]: ...
60
66
 
61
- def query_step[T: Query](
67
+ @overload
68
+ def query_step(self, wrapped: None = ..., /) -> Decorator: ...
69
+
70
+ def query_step[T: Query]( # type: ignore[misc]
62
71
  self,
63
- wrapped: PipeConverterMethod[T, Any] | None = None,
72
+ wrapped: ConvertMethod[T, Any] | None = None,
64
73
  /,
65
74
  ) -> Any:
66
75
  return self.step(wrapped, dispatcher=self.__query_dispatcher)
File without changes
@@ -22,7 +22,7 @@ test = [
22
22
 
23
23
  [project]
24
24
  name = "python-cq"
25
- version = "0.14.0"
25
+ version = "0.14.1"
26
26
  description = "CQRS library for async Python projects."
27
27
  license = "MIT"
28
28
  license-files = ["LICENSE"]
@@ -1,210 +0,0 @@
1
- from collections import deque
2
- from collections.abc import Awaitable, Callable
3
- from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Protocol, Self, overload
5
-
6
- from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
7
- from cq._core.middleware import Middleware
8
-
9
- type PipeConverter[I, O] = Callable[[O], Awaitable[I]]
10
-
11
-
12
- class PipeConverterMethod[I, O](Protocol):
13
- def __get__(
14
- self,
15
- instance: object,
16
- owner: type | None = ...,
17
- ) -> PipeConverter[I, O]: ...
18
-
19
-
20
- @dataclass(repr=False, eq=False, frozen=True, slots=True)
21
- class PipeStep[I, O]:
22
- converter: PipeConverter[I, O]
23
- dispatcher: Dispatcher[I, Any] | None = field(default=None)
24
-
25
-
26
- class Pipe[I, O](BaseDispatcher[I, O]):
27
- __slots__ = ("__dispatcher", "__steps")
28
-
29
- __dispatcher: Dispatcher[Any, Any]
30
- __steps: list[PipeStep[Any, Any]]
31
-
32
- def __init__(self, dispatcher: Dispatcher[Any, Any]) -> None:
33
- super().__init__()
34
- self.__dispatcher = dispatcher
35
- self.__steps = []
36
-
37
- if TYPE_CHECKING: # pragma: no cover
38
-
39
- @overload
40
- def step[T](
41
- self,
42
- wrapped: PipeConverter[T, Any],
43
- /,
44
- *,
45
- dispatcher: Dispatcher[T, Any] | None = ...,
46
- ) -> PipeConverter[T, Any]: ...
47
-
48
- @overload
49
- def step[T](
50
- self,
51
- wrapped: None = ...,
52
- /,
53
- *,
54
- dispatcher: Dispatcher[T, Any] | None = ...,
55
- ) -> Callable[[PipeConverter[T, Any]], PipeConverter[T, Any]]: ...
56
-
57
- def step[T](
58
- self,
59
- wrapped: PipeConverter[T, Any] | None = None,
60
- /,
61
- *,
62
- dispatcher: Dispatcher[T, Any] | None = None,
63
- ) -> Any:
64
- def decorator(wp: PipeConverter[T, Any]) -> PipeConverter[T, Any]:
65
- step = PipeStep(wp, dispatcher)
66
- self.__steps.append(step)
67
- return wp
68
-
69
- return decorator(wrapped) if wrapped else decorator
70
-
71
- def add_static_step[T](
72
- self,
73
- input_value: T,
74
- *,
75
- dispatcher: Dispatcher[T, Any] | None = None,
76
- ) -> Self:
77
- @self.step(dispatcher=dispatcher)
78
- async def converter(_: Any) -> T:
79
- return input_value
80
-
81
- return self
82
-
83
- async def dispatch(self, input_value: I, /) -> O:
84
- return await self._invoke_with_middlewares(self.__execute, input_value)
85
-
86
- async def __execute(self, input_value: I) -> O:
87
- dispatcher = self.__dispatcher
88
-
89
- for step in self.__steps:
90
- output_value = await dispatcher.dispatch(input_value)
91
- input_value = await step.converter(output_value)
92
-
93
- if input_value is None:
94
- return NotImplemented
95
-
96
- dispatcher = step.dispatcher or self.__dispatcher
97
-
98
- return await dispatcher.dispatch(input_value)
99
-
100
-
101
- @dataclass(repr=False, eq=False, frozen=True, slots=True)
102
- class ContextPipelineStep[I, O]:
103
- converter: PipeConverterMethod[I, O]
104
- dispatcher: Dispatcher[I, Any] | None = field(default=None)
105
-
106
-
107
- class ContextPipeline[I]:
108
- __slots__ = ("__dispatcher", "__middlewares", "__steps")
109
-
110
- __dispatcher: Dispatcher[Any, Any]
111
- __middlewares: deque[Middleware[Any, Any]]
112
- __steps: list[ContextPipelineStep[Any, Any]]
113
-
114
- def __init__(self, dispatcher: Dispatcher[Any, Any]) -> None:
115
- self.__dispatcher = dispatcher
116
- self.__middlewares = deque()
117
- self.__steps = []
118
-
119
- if TYPE_CHECKING: # pragma: no cover
120
-
121
- @overload
122
- def __get__[O](self, instance: None, owner: type[O], /) -> Dispatcher[I, O]: ...
123
-
124
- @overload
125
- def __get__[O](
126
- self,
127
- instance: O,
128
- owner: type[O] | None = ...,
129
- /,
130
- ) -> Dispatcher[I, O]: ...
131
-
132
- @overload
133
- def __get__(self, instance: None = ..., owner: None = ..., /) -> Self: ...
134
-
135
- def __get__[O](
136
- self,
137
- instance: O | None = None,
138
- owner: type[O] | None = None,
139
- /,
140
- ) -> Self | Dispatcher[I, O]:
141
- if instance is None:
142
- if owner is None:
143
- return self
144
-
145
- instance = owner()
146
-
147
- pipeline = self.__new_pipeline(instance, owner)
148
- return BoundContextPipeline(instance, pipeline)
149
-
150
- def add_middlewares(self, *middlewares: Middleware[[I], Any]) -> Self:
151
- self.__middlewares.extendleft(reversed(middlewares))
152
- return self
153
-
154
- if TYPE_CHECKING: # pragma: no cover
155
-
156
- @overload
157
- def step[T](
158
- self,
159
- wrapped: PipeConverterMethod[T, Any],
160
- /,
161
- *,
162
- dispatcher: Dispatcher[T, Any] | None = ...,
163
- ) -> PipeConverterMethod[T, Any]: ...
164
-
165
- @overload
166
- def step[T](
167
- self,
168
- wrapped: None = ...,
169
- /,
170
- *,
171
- dispatcher: Dispatcher[T, Any] | None = ...,
172
- ) -> Callable[[PipeConverterMethod[T, Any]], PipeConverterMethod[T, Any]]: ...
173
-
174
- def step[T](
175
- self,
176
- wrapped: PipeConverterMethod[T, Any] | None = None,
177
- /,
178
- *,
179
- dispatcher: Dispatcher[T, Any] | None = None,
180
- ) -> Any:
181
- def decorator(wp: PipeConverterMethod[T, Any]) -> PipeConverterMethod[T, Any]:
182
- step = ContextPipelineStep(wp, dispatcher)
183
- self.__steps.append(step)
184
- return wp
185
-
186
- return decorator(wrapped) if wrapped else decorator
187
-
188
- def __new_pipeline[T](
189
- self,
190
- context: T,
191
- context_type: type[T] | None,
192
- ) -> Pipe[I, Any]:
193
- pipeline: Pipe[I, Any] = Pipe(self.__dispatcher)
194
- pipeline.add_middlewares(*self.__middlewares)
195
-
196
- for step in self.__steps:
197
- converter = step.converter.__get__(context, context_type)
198
- pipeline.step(converter, dispatcher=step.dispatcher)
199
-
200
- return pipeline
201
-
202
-
203
- @dataclass(repr=False, eq=False, frozen=True, slots=True)
204
- class BoundContextPipeline[I, O](Dispatcher[I, O]):
205
- context: O
206
- pipeline: Pipe[I, Any]
207
-
208
- async def dispatch(self, input_value: I, /) -> O:
209
- await self.pipeline.dispatch(input_value)
210
- return self.context
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes