python-cq 0.13.0__tar.gz → 0.14.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.
Files changed (26) hide show
  1. {python_cq-0.13.0 → python_cq-0.14.0}/PKG-INFO +1 -1
  2. python_cq-0.14.0/cq/_core/middleware.py +106 -0
  3. {python_cq-0.13.0 → python_cq-0.14.0}/pyproject.toml +1 -1
  4. python_cq-0.13.0/cq/_core/middleware.py +0 -69
  5. {python_cq-0.13.0 → python_cq-0.14.0}/.gitignore +0 -0
  6. {python_cq-0.13.0 → python_cq-0.14.0}/LICENSE +0 -0
  7. {python_cq-0.13.0 → python_cq-0.14.0}/cq/__init__.py +0 -0
  8. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/__init__.py +0 -0
  9. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/dispatcher/__init__.py +0 -0
  10. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/dispatcher/base.py +0 -0
  11. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/dispatcher/bus.py +0 -0
  12. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/dispatcher/lazy.py +0 -0
  13. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/dispatcher/pipe.py +0 -0
  14. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/handler.py +0 -0
  15. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/message.py +0 -0
  16. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/pipetools.py +0 -0
  17. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/related_events.py +0 -0
  18. {python_cq-0.13.0 → python_cq-0.14.0}/cq/_core/scope.py +0 -0
  19. {python_cq-0.13.0 → python_cq-0.14.0}/cq/exceptions.py +0 -0
  20. {python_cq-0.13.0 → python_cq-0.14.0}/cq/ext/__init__.py +0 -0
  21. {python_cq-0.13.0 → python_cq-0.14.0}/cq/ext/fastapi.py +0 -0
  22. {python_cq-0.13.0 → python_cq-0.14.0}/cq/middlewares/__init__.py +0 -0
  23. {python_cq-0.13.0 → python_cq-0.14.0}/cq/middlewares/retry.py +0 -0
  24. {python_cq-0.13.0 → python_cq-0.14.0}/cq/middlewares/scope.py +0 -0
  25. {python_cq-0.13.0 → python_cq-0.14.0}/cq/py.typed +0 -0
  26. {python_cq-0.13.0 → python_cq-0.14.0}/docs/index.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cq
3
- Version: 0.13.0
3
+ Version: 0.14.0
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,106 @@
1
+ from collections.abc import AsyncGenerator, Awaitable, Callable
2
+ from dataclasses import dataclass, field
3
+ from inspect import isasyncgenfunction
4
+ from typing import Concatenate, Self, TypeGuard
5
+
6
+ from cq.exceptions import MiddlewareError
7
+
8
+ type MiddlewareResult[T] = AsyncGenerator[None, T]
9
+ type GeneratorMiddleware[**P, T] = Callable[P, MiddlewareResult[T]]
10
+ type ClassicMiddleware[**P, T] = Callable[
11
+ Concatenate[Callable[P, Awaitable[T]], P], Awaitable[T]
12
+ ]
13
+
14
+ type Middleware[**P, T] = ClassicMiddleware[P, T] | GeneratorMiddleware[P, T]
15
+
16
+
17
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
18
+ class MiddlewareGroup[**P, T]:
19
+ __middlewares: list[ClassicMiddleware[P, T]] = field(
20
+ default_factory=list,
21
+ init=False,
22
+ )
23
+
24
+ def add(self, *middlewares: Middleware[P, T]) -> Self:
25
+ classic_middlewares = reversed(
26
+ tuple(self.__normalize(middleware) for middleware in middlewares)
27
+ )
28
+ self.__middlewares.extend(classic_middlewares)
29
+ return self
30
+
31
+ async def invoke(
32
+ self,
33
+ handler: Callable[P, Awaitable[T]],
34
+ /,
35
+ *args: P.args,
36
+ **kwargs: P.kwargs,
37
+ ) -> T:
38
+ return await self.__apply_stack(handler)(*args, **kwargs)
39
+
40
+ def __apply_stack(
41
+ self,
42
+ handler: Callable[P, Awaitable[T]],
43
+ ) -> Callable[P, Awaitable[T]]:
44
+ for middleware in self.__middlewares:
45
+ handler = _BoundMiddleware(handler, middleware)
46
+
47
+ return handler
48
+
49
+ @staticmethod
50
+ def __normalize(middleware: Middleware[P, T]) -> ClassicMiddleware[P, T]:
51
+ if _is_gen_middleware(middleware):
52
+ return _GeneratorMiddleware(middleware)
53
+
54
+ return middleware # type: ignore[return-value]
55
+
56
+
57
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
58
+ class _BoundMiddleware[**P, T]:
59
+ call_next: Callable[P, Awaitable[T]]
60
+ middleware: ClassicMiddleware[P, T]
61
+
62
+ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
63
+ return await self.middleware(self.call_next, *args, **kwargs)
64
+
65
+
66
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
67
+ class _GeneratorMiddleware[**P, T]:
68
+ middleware: GeneratorMiddleware[P, T]
69
+
70
+ async def __call__(
71
+ self,
72
+ call_next: Callable[P, Awaitable[T]],
73
+ /,
74
+ *args: P.args,
75
+ **kwargs: P.kwargs,
76
+ ) -> T:
77
+ generator: MiddlewareResult[T] = self.middleware(*args, **kwargs)
78
+ value: T = NotImplemented
79
+
80
+ try:
81
+ await anext(generator)
82
+
83
+ while True:
84
+ try:
85
+ value = await call_next(*args, **kwargs)
86
+ except BaseException as exc:
87
+ await generator.athrow(exc)
88
+ else:
89
+ await generator.asend(value)
90
+ raise MiddlewareError(
91
+ f"Too many `yield` keywords in `{self.middleware}`."
92
+ )
93
+
94
+ except StopAsyncIteration:
95
+ ...
96
+
97
+ finally:
98
+ await generator.aclose()
99
+
100
+ return value
101
+
102
+
103
+ def _is_gen_middleware[**P, T](
104
+ middleware: Middleware[P, T],
105
+ ) -> TypeGuard[GeneratorMiddleware[P, T]]:
106
+ return any(map(isasyncgenfunction, (middleware, middleware.__call__))) # type: ignore[operator]
@@ -22,7 +22,7 @@ test = [
22
22
 
23
23
  [project]
24
24
  name = "python-cq"
25
- version = "0.13.0"
25
+ version = "0.14.0"
26
26
  description = "CQRS library for async Python projects."
27
27
  license = "MIT"
28
28
  license-files = ["LICENSE"]
@@ -1,69 +0,0 @@
1
- from collections.abc import AsyncGenerator, Awaitable, Callable
2
- from dataclasses import dataclass, field
3
- from typing import Self
4
-
5
- from cq.exceptions import MiddlewareError
6
-
7
- type MiddlewareResult[T] = AsyncGenerator[None, T]
8
- type Middleware[**P, T] = Callable[P, MiddlewareResult[T]]
9
-
10
-
11
- @dataclass(repr=False, eq=False, frozen=True, slots=True)
12
- class MiddlewareGroup[**P, T]:
13
- __middlewares: list[Middleware[P, T]] = field(default_factory=list, init=False)
14
-
15
- def add(self, *middlewares: Middleware[P, T]) -> Self:
16
- self.__middlewares.extend(reversed(middlewares))
17
- return self
18
-
19
- async def invoke(
20
- self,
21
- handler: Callable[P, Awaitable[T]],
22
- /,
23
- *args: P.args,
24
- **kwargs: P.kwargs,
25
- ) -> T:
26
- return await self.__apply_stack(handler)(*args, **kwargs)
27
-
28
- def __apply_stack(
29
- self,
30
- handler: Callable[P, Awaitable[T]],
31
- ) -> Callable[P, Awaitable[T]]:
32
- for middleware in self.__middlewares:
33
- handler = self.__apply_middleware(handler, middleware)
34
-
35
- return handler
36
-
37
- @classmethod
38
- def __apply_middleware(
39
- cls,
40
- handler: Callable[P, Awaitable[T]],
41
- middleware: Middleware[P, T],
42
- ) -> Callable[P, Awaitable[T]]:
43
- async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
44
- generator: MiddlewareResult[T] = middleware(*args, **kwargs)
45
- value: T = NotImplemented
46
-
47
- try:
48
- await anext(generator)
49
-
50
- while True:
51
- try:
52
- value = await handler(*args, **kwargs)
53
- except BaseException as exc:
54
- await generator.athrow(exc)
55
- else:
56
- await generator.asend(value)
57
- raise MiddlewareError(
58
- f"Too many `yield` keywords in `{middleware}`."
59
- )
60
-
61
- except StopAsyncIteration:
62
- ...
63
-
64
- finally:
65
- await generator.aclose()
66
-
67
- return value
68
-
69
- return wrapper
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes