python-cq 0.25.1__tar.gz → 0.26.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.
- {python_cq-0.25.1 → python_cq-0.26.0}/PKG-INFO +17 -10
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/__init__.py +7 -1
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/middleware.py +27 -5
- {python_cq-0.25.1 → python_cq-0.26.0}/docs/index.md +16 -9
- {python_cq-0.25.1 → python_cq-0.26.0}/pyproject.toml +1 -1
- {python_cq-0.25.1 → python_cq-0.26.0}/.gitignore +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/LICENSE +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/common/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/common/typing.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/message.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/queuing/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/queuing/pump.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/queuing/queues/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/queuing/queues/abc.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/queuing/queues/memory.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/related_events.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/command_pipeline.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/di.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/dispatchers/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/dispatchers/abc.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/dispatchers/bus.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/dispatchers/functions.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/dispatchers/lazy.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/dispatchers/pipe.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/handler.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/_core/routing/router.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/exceptions.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/ext/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/ext/injection.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/middlewares/__init__.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/middlewares/contextlib.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/middlewares/exc.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/middlewares/retry.py +0 -0
- {python_cq-0.25.1 → python_cq-0.26.0}/cq/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-cq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.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
|
|
@@ -35,7 +35,14 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
[](https://pypi.org/project/python-cq)
|
|
36
36
|
[](https://pypistats.org/packages/python-cq)
|
|
37
37
|
|
|
38
|
-
**python-cq** is an async-first Python library for organizing code around CQRS. It
|
|
38
|
+
**python-cq** is an async-first Python library for organizing code around CQRS. It routes commands, queries, and events to their handlers through dedicated buses, and lets you put a queue in front of any of them when a message should be processed later. Dependency injection is pluggable behind a small protocol.
|
|
39
|
+
|
|
40
|
+
The library is made of two independent parts:
|
|
41
|
+
|
|
42
|
+
* **Routing**: messages, handlers, buses, middlewares, and command pipelines. This is the core of the library and what most applications need.
|
|
43
|
+
* **Queuing**: a `Queue` protocol and a `Pump` that drains a queue into any async callable, typically a bus.
|
|
44
|
+
|
|
45
|
+
You can use routing without ever touching queuing, and the queuing primitives work with any dispatcher, not only the buses of this library.
|
|
39
46
|
|
|
40
47
|
## What is CQRS?
|
|
41
48
|
|
|
@@ -49,13 +56,13 @@ CQRS is often discussed alongside distributed systems and Event Sourcing, but th
|
|
|
49
56
|
|
|
50
57
|
## Three message types
|
|
51
58
|
|
|
52
|
-
| Type | Intent
|
|
53
|
-
|
|
54
|
-
| `Command` | Change the state of the system
|
|
55
|
-
| `Query` | Read state without side effects
|
|
56
|
-
| `Event` | Notify that something has happened
|
|
59
|
+
| Type | Intent | Handlers | Returns |
|
|
60
|
+
|-----------|------------------------------------|--------------------|----------------------------|
|
|
61
|
+
| `Command` | Change the state of the system | Exactly one | The handler's return value |
|
|
62
|
+
| `Query` | Read state without side effects | Exactly one | The handler's return value |
|
|
63
|
+
| `Event` | Notify that something has happened | Zero, one, or many | Nothing |
|
|
57
64
|
|
|
58
|
-
A `Command` is allowed to return a value (for convenience, typically an id or a result object), but that does not
|
|
65
|
+
A `Command` is allowed to return a value (for convenience, typically an id or a result object), but that does not make it a query. Keep intent clear.
|
|
59
66
|
|
|
60
67
|
## Installation
|
|
61
68
|
|
|
@@ -67,7 +74,7 @@ With the default DI backend ([python-injection](https://github.com/100nm/python-
|
|
|
67
74
|
pip install "python-cq[injection]"
|
|
68
75
|
```
|
|
69
76
|
|
|
70
|
-
Without dependency injection (you will need to implement a `DIAdapter`):
|
|
77
|
+
Without dependency injection (you will need to implement a `DIAdapter` to inject anything into your handlers):
|
|
71
78
|
|
|
72
79
|
```bash
|
|
73
80
|
pip install python-cq
|
|
@@ -105,7 +112,7 @@ async def main(bus: CommandBus[int]) -> None:
|
|
|
105
112
|
asyncio.run(main())
|
|
106
113
|
```
|
|
107
114
|
|
|
108
|
-
The decorator registers the handler against the type of
|
|
115
|
+
The decorator registers the handler against the type annotation of the first `handle` parameter. The bus is resolved by the DI container and forwards the command to that handler.
|
|
109
116
|
|
|
110
117
|
## Prerequisites
|
|
111
118
|
|
|
@@ -7,7 +7,12 @@ from ._core.message import (
|
|
|
7
7
|
Query,
|
|
8
8
|
QueryBus,
|
|
9
9
|
)
|
|
10
|
-
from ._core.middleware import
|
|
10
|
+
from ._core.middleware import (
|
|
11
|
+
Middleware,
|
|
12
|
+
MiddlewareResult,
|
|
13
|
+
resolve_handler_source,
|
|
14
|
+
through_middlewares,
|
|
15
|
+
)
|
|
11
16
|
from ._core.queuing.pump import Pump
|
|
12
17
|
from ._core.queuing.queues.abc import Consumer, Delivery, Producer, Queue
|
|
13
18
|
from ._core.queuing.queues.memory import MemoryQueue
|
|
@@ -54,6 +59,7 @@ __all__ = (
|
|
|
54
59
|
"new_query_bus",
|
|
55
60
|
"query_handler",
|
|
56
61
|
"resolve_handler_source",
|
|
62
|
+
"through_middlewares",
|
|
57
63
|
)
|
|
58
64
|
|
|
59
65
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
|
+
from functools import wraps
|
|
3
4
|
from inspect import isasyncgenfunction
|
|
4
5
|
from typing import Any, Concatenate, Self, TypeGuard
|
|
5
6
|
|
|
@@ -23,6 +24,13 @@ class MiddlewareGroup[**P, T]:
|
|
|
23
24
|
init=False,
|
|
24
25
|
)
|
|
25
26
|
|
|
27
|
+
def __call__(self, wrapped: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
|
|
28
|
+
@wraps(wrapped)
|
|
29
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
30
|
+
return await self.invoke(wrapped, *args, **kwargs)
|
|
31
|
+
|
|
32
|
+
return wrapper
|
|
33
|
+
|
|
26
34
|
def add(self, *middlewares: Middleware[P, T]) -> Self:
|
|
27
35
|
classic_middlewares = reversed(
|
|
28
36
|
tuple(self.__normalize(middleware) for middleware in middlewares)
|
|
@@ -48,6 +56,10 @@ class MiddlewareGroup[**P, T]:
|
|
|
48
56
|
|
|
49
57
|
return handler
|
|
50
58
|
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_middlewares(cls, *middlewares: Middleware[P, T]) -> Self:
|
|
61
|
+
return cls().add(*middlewares)
|
|
62
|
+
|
|
51
63
|
@staticmethod
|
|
52
64
|
def __normalize(middleware: Middleware[P, T]) -> ClassicMiddleware[P, T]:
|
|
53
65
|
if _is_gen_middleware(middleware):
|
|
@@ -56,6 +68,14 @@ class MiddlewareGroup[**P, T]:
|
|
|
56
68
|
return middleware # type: ignore[return-value]
|
|
57
69
|
|
|
58
70
|
|
|
71
|
+
def through_middlewares[**P, T](
|
|
72
|
+
first_middleware: Middleware[P, T],
|
|
73
|
+
/,
|
|
74
|
+
*middlewares: Middleware[P, T],
|
|
75
|
+
) -> MiddlewareGroup[P, T]:
|
|
76
|
+
return MiddlewareGroup.from_middlewares(first_middleware, *middlewares)
|
|
77
|
+
|
|
78
|
+
|
|
59
79
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
60
80
|
class _BoundMiddleware[**P, T]:
|
|
61
81
|
call_next: Callable[P, Awaitable[T]]
|
|
@@ -71,11 +91,13 @@ def resolve_handler_source[**P, T](
|
|
|
71
91
|
| HandleFunction[P, T],
|
|
72
92
|
/,
|
|
73
93
|
) -> HandlerType[P, T] | Any:
|
|
74
|
-
while
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
while isinstance(call_next, _BoundMiddleware):
|
|
95
|
+
call_next = call_next.call_next
|
|
96
|
+
|
|
97
|
+
if isinstance(call_next, HandleFunction):
|
|
98
|
+
return call_next.source
|
|
99
|
+
|
|
100
|
+
return call_next
|
|
79
101
|
|
|
80
102
|
|
|
81
103
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
[](https://pypi.org/project/python-cq)
|
|
4
4
|
[](https://pypistats.org/packages/python-cq)
|
|
5
5
|
|
|
6
|
-
**python-cq** is an async-first Python library for organizing code around CQRS. It
|
|
6
|
+
**python-cq** is an async-first Python library for organizing code around CQRS. It routes commands, queries, and events to their handlers through dedicated buses, and lets you put a queue in front of any of them when a message should be processed later. Dependency injection is pluggable behind a small protocol.
|
|
7
|
+
|
|
8
|
+
The library is made of two independent parts:
|
|
9
|
+
|
|
10
|
+
* **Routing**: messages, handlers, buses, middlewares, and command pipelines. This is the core of the library and what most applications need.
|
|
11
|
+
* **Queuing**: a `Queue` protocol and a `Pump` that drains a queue into any async callable, typically a bus.
|
|
12
|
+
|
|
13
|
+
You can use routing without ever touching queuing, and the queuing primitives work with any dispatcher, not only the buses of this library.
|
|
7
14
|
|
|
8
15
|
## What is CQRS?
|
|
9
16
|
|
|
@@ -17,13 +24,13 @@ CQRS is often discussed alongside distributed systems and Event Sourcing, but th
|
|
|
17
24
|
|
|
18
25
|
## Three message types
|
|
19
26
|
|
|
20
|
-
| Type | Intent
|
|
21
|
-
|
|
22
|
-
| `Command` | Change the state of the system
|
|
23
|
-
| `Query` | Read state without side effects
|
|
24
|
-
| `Event` | Notify that something has happened
|
|
27
|
+
| Type | Intent | Handlers | Returns |
|
|
28
|
+
|-----------|------------------------------------|--------------------|----------------------------|
|
|
29
|
+
| `Command` | Change the state of the system | Exactly one | The handler's return value |
|
|
30
|
+
| `Query` | Read state without side effects | Exactly one | The handler's return value |
|
|
31
|
+
| `Event` | Notify that something has happened | Zero, one, or many | Nothing |
|
|
25
32
|
|
|
26
|
-
A `Command` is allowed to return a value (for convenience, typically an id or a result object), but that does not
|
|
33
|
+
A `Command` is allowed to return a value (for convenience, typically an id or a result object), but that does not make it a query. Keep intent clear.
|
|
27
34
|
|
|
28
35
|
## Installation
|
|
29
36
|
|
|
@@ -35,7 +42,7 @@ With the default DI backend ([python-injection](https://github.com/100nm/python-
|
|
|
35
42
|
pip install "python-cq[injection]"
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
Without dependency injection (you will need to implement a `DIAdapter`):
|
|
45
|
+
Without dependency injection (you will need to implement a `DIAdapter` to inject anything into your handlers):
|
|
39
46
|
|
|
40
47
|
```bash
|
|
41
48
|
pip install python-cq
|
|
@@ -73,7 +80,7 @@ async def main(bus: CommandBus[int]) -> None:
|
|
|
73
80
|
asyncio.run(main())
|
|
74
81
|
```
|
|
75
82
|
|
|
76
|
-
The decorator registers the handler against the type of
|
|
83
|
+
The decorator registers the handler against the type annotation of the first `handle` parameter. The bus is resolved by the DI container and forwards the command to that handler.
|
|
77
84
|
|
|
78
85
|
## Prerequisites
|
|
79
86
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|