python-cq 0.15.0__tar.gz → 0.15.2__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 (27) hide show
  1. {python_cq-0.15.0 → python_cq-0.15.2}/PKG-INFO +1 -1
  2. {python_cq-0.15.0 → python_cq-0.15.2}/cq/__init__.py +2 -1
  3. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/dispatcher/bus.py +13 -3
  4. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/dispatcher/pipe.py +23 -18
  5. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/handler.py +16 -7
  6. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/middleware.py +15 -1
  7. {python_cq-0.15.0 → python_cq-0.15.2}/pyproject.toml +1 -1
  8. {python_cq-0.15.0 → python_cq-0.15.2}/.gitignore +0 -0
  9. {python_cq-0.15.0 → python_cq-0.15.2}/LICENSE +0 -0
  10. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/__init__.py +0 -0
  11. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/common/__init__.py +0 -0
  12. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/common/typing.py +0 -0
  13. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/dispatcher/__init__.py +0 -0
  14. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/dispatcher/base.py +0 -0
  15. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/dispatcher/lazy.py +0 -0
  16. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/message.py +0 -0
  17. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/pipetools.py +0 -0
  18. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/related_events.py +0 -0
  19. {python_cq-0.15.0 → python_cq-0.15.2}/cq/_core/scope.py +0 -0
  20. {python_cq-0.15.0 → python_cq-0.15.2}/cq/exceptions.py +0 -0
  21. {python_cq-0.15.0 → python_cq-0.15.2}/cq/ext/__init__.py +0 -0
  22. {python_cq-0.15.0 → python_cq-0.15.2}/cq/ext/fastapi.py +0 -0
  23. {python_cq-0.15.0 → python_cq-0.15.2}/cq/middlewares/__init__.py +0 -0
  24. {python_cq-0.15.0 → python_cq-0.15.2}/cq/middlewares/retry.py +0 -0
  25. {python_cq-0.15.0 → python_cq-0.15.2}/cq/middlewares/scope.py +0 -0
  26. {python_cq-0.15.0 → python_cq-0.15.2}/cq/py.typed +0 -0
  27. {python_cq-0.15.0 → python_cq-0.15.2}/docs/index.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cq
3
- Version: 0.15.0
3
+ Version: 0.15.2
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
@@ -17,7 +17,7 @@ from ._core.message import (
17
17
  new_query_bus,
18
18
  query_handler,
19
19
  )
20
- from ._core.middleware import Middleware, MiddlewareResult
20
+ from ._core.middleware import Middleware, MiddlewareResult, resolve_handler_source
21
21
  from ._core.pipetools import ContextCommandPipeline
22
22
  from ._core.related_events import RelatedEvents
23
23
  from ._core.scope import CQScope
@@ -47,4 +47,5 @@ __all__ = (
47
47
  "new_event_bus",
48
48
  "new_query_bus",
49
49
  "query_handler",
50
+ "resolve_handler_source",
50
51
  )
@@ -31,7 +31,12 @@ class Bus[I, O](Dispatcher[I, O], Protocol):
31
31
  raise NotImplementedError
32
32
 
33
33
  @abstractmethod
34
- def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
34
+ def subscribe(
35
+ self,
36
+ input_type: type[I],
37
+ factory: HandlerFactory[[I], O],
38
+ fail_silently: bool = ...,
39
+ ) -> Self:
35
40
  raise NotImplementedError
36
41
 
37
42
 
@@ -50,8 +55,13 @@ class BaseBus[I, O](BaseDispatcher[I, O], Bus[I, O], ABC):
50
55
  self.__listeners.extend(listeners)
51
56
  return self
52
57
 
53
- def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
54
- self.__registry.subscribe(input_type, factory)
58
+ def subscribe(
59
+ self,
60
+ input_type: type[I],
61
+ factory: HandlerFactory[[I], O],
62
+ fail_silently: bool = False,
63
+ ) -> Self:
64
+ self.__registry.subscribe(input_type, factory, fail_silently=fail_silently)
55
65
  return self
56
66
 
57
67
  def _handlers_from(self, input_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
@@ -152,25 +152,30 @@ class ContextPipeline[I]:
152
152
  if TYPE_CHECKING: # pragma: no cover
153
153
 
154
154
  @overload
155
- def __get__[O](self, instance: None, owner: type[O], /) -> Dispatcher[I, O]: ...
155
+ def __get__[Context](
156
+ self,
157
+ instance: None,
158
+ owner: type[Context],
159
+ /,
160
+ ) -> Dispatcher[I, Context]: ...
156
161
 
157
162
  @overload
158
- def __get__[O](
163
+ def __get__[Context](
159
164
  self,
160
- instance: O,
161
- owner: type[O] | None = ...,
165
+ instance: Context,
166
+ owner: type[Context] | None = ...,
162
167
  /,
163
- ) -> Dispatcher[I, O]: ...
168
+ ) -> Dispatcher[I, Context]: ...
164
169
 
165
170
  @overload
166
171
  def __get__(self, instance: None = ..., owner: None = ..., /) -> Self: ...
167
172
 
168
- def __get__[O](
173
+ def __get__[Context](
169
174
  self,
170
- instance: O | None = None,
171
- owner: type[O] | None = None,
175
+ instance: Context | None = None,
176
+ owner: type[Context] | None = None,
172
177
  /,
173
- ) -> Self | Dispatcher[I, O]:
178
+ ) -> Self | Dispatcher[I, Context]:
174
179
  if instance is None:
175
180
  if owner is None:
176
181
  return self
@@ -231,19 +236,19 @@ class ContextPipeline[I]:
231
236
 
232
237
  return decorator(wrapped) if wrapped else decorator
233
238
 
234
- async def __execute[O](
239
+ async def __execute[Context](
235
240
  self,
236
241
  input_value: I,
237
242
  /,
238
243
  *,
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
244
+ context: Context,
245
+ context_type: type[Context] | None,
246
+ ) -> Context:
247
+ async def handler(i: I, /) -> Context:
248
+ await self.__steps.execute(i, context, context_type)
249
+ return context
250
+
251
+ return await self.__middleware_group.invoke(handler, input_value)
247
252
 
248
253
 
249
254
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -3,7 +3,7 @@ from collections import defaultdict
3
3
  from collections.abc import Awaitable, Callable, Iterator
4
4
  from dataclasses import dataclass, field
5
5
  from functools import partial
6
- from inspect import Parameter, isclass
6
+ from inspect import Parameter, isclass, unwrap
7
7
  from inspect import signature as inspect_signature
8
8
  from typing import TYPE_CHECKING, Any, Protocol, Self, overload, runtime_checkable
9
9
 
@@ -27,14 +27,23 @@ class Handler[**P, T](Protocol):
27
27
 
28
28
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
29
29
  class HandleFunction[**P, T]:
30
- handler_factory: HandlerFactory[P, T]
31
- handler_type: HandlerType[P, T] | None = field(default=None)
32
- fail_silently: bool = field(default=False)
30
+ factory: HandlerFactory[P, T]
31
+ source: HandlerType[P, T] | Any
32
+ fail_silently: bool
33
33
 
34
34
  async def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
35
- handler = await self.handler_factory()
35
+ handler = await self.factory()
36
36
  return await handler.handle(*args, **kwargs)
37
37
 
38
+ @classmethod
39
+ def create(
40
+ cls,
41
+ factory: HandlerFactory[P, T],
42
+ source: HandlerType[P, T] | None = None,
43
+ fail_silently: bool = False,
44
+ ) -> Self:
45
+ return cls(factory, source or unwrap(factory), fail_silently)
46
+
38
47
 
39
48
  @runtime_checkable
40
49
  class HandlerRegistry[I, O](Protocol):
@@ -73,7 +82,7 @@ class MultipleHandlerRegistry[I, O](HandlerRegistry[I, O]):
73
82
  handler_type: HandlerType[[I], O] | None = None,
74
83
  fail_silently: bool = False,
75
84
  ) -> Self:
76
- function = HandleFunction(handler_factory, handler_type, fail_silently)
85
+ function = HandleFunction.create(handler_factory, handler_type, fail_silently)
77
86
 
78
87
  for key_type in _build_key_types(input_type):
79
88
  self.__values[key_type].append(function)
@@ -101,7 +110,7 @@ class SingleHandlerRegistry[I, O](HandlerRegistry[I, O]):
101
110
  handler_type: HandlerType[[I], O] | None = None,
102
111
  fail_silently: bool = False,
103
112
  ) -> Self:
104
- function = HandleFunction(handler_factory, handler_type, fail_silently)
113
+ function = HandleFunction.create(handler_factory, handler_type, fail_silently)
105
114
  entries = {key_type: function for key_type in _build_key_types(input_type)}
106
115
 
107
116
  for key_type in entries:
@@ -1,8 +1,9 @@
1
1
  from collections.abc import AsyncGenerator, Awaitable, Callable
2
2
  from dataclasses import dataclass, field
3
3
  from inspect import isasyncgenfunction
4
- from typing import Concatenate, Self, TypeGuard
4
+ from typing import Any, Concatenate, Self, TypeGuard
5
5
 
6
+ from cq._core.handler import HandleFunction, HandlerType
6
7
  from cq.exceptions import MiddlewareError
7
8
 
8
9
  type MiddlewareResult[T] = AsyncGenerator[None, T]
@@ -63,6 +64,19 @@ class _BoundMiddleware[**P, T]:
63
64
  return await self.middleware(self.call_next, *args, **kwargs)
64
65
 
65
66
 
67
+ def resolve_handler_source[**P, T](
68
+ call_next: Callable[P, Awaitable[T]]
69
+ | _BoundMiddleware[P, T]
70
+ | HandleFunction[P, T],
71
+ /,
72
+ ) -> HandlerType[P, T] | Any:
73
+ while True:
74
+ try:
75
+ call_next = call_next.call_next # type: ignore[union-attr]
76
+ except AttributeError:
77
+ return call_next.source # type: ignore[union-attr]
78
+
79
+
66
80
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
67
81
  class _GeneratorMiddleware[**P, T]:
68
82
  middleware: GeneratorMiddleware[P, T]
@@ -22,7 +22,7 @@ test = [
22
22
 
23
23
  [project]
24
24
  name = "python-cq"
25
- version = "0.15.0"
25
+ version = "0.15.2"
26
26
  description = "CQRS library for async Python projects."
27
27
  license = "MIT"
28
28
  license-files = ["LICENSE"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes