python-cq 0.25.0__py3-none-any.whl → 0.25.2__py3-none-any.whl

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.
cq/_core/middleware.py CHANGED
@@ -71,11 +71,13 @@ def resolve_handler_source[**P, T](
71
71
  | HandleFunction[P, T],
72
72
  /,
73
73
  ) -> HandlerType[P, T] | Any:
74
- while True:
75
- try:
76
- call_next = call_next.call_next # type: ignore[union-attr]
77
- except AttributeError:
78
- return call_next.source # type: ignore[union-attr]
74
+ while isinstance(call_next, _BoundMiddleware):
75
+ call_next = call_next.call_next
76
+
77
+ if isinstance(call_next, HandleFunction):
78
+ return call_next.source
79
+
80
+ return call_next
79
81
 
80
82
 
81
83
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -25,7 +25,7 @@ class AnyIORelatedEvents(RelatedEvents):
25
25
  task_group: TaskGroup = field(default_factory=create_task_group)
26
26
  history: list[Event] = field(default_factory=list, init=False)
27
27
 
28
- def __bool__(self) -> bool: # pragma: no cover
28
+ def __bool__(self) -> bool:
29
29
  return bool(self.history)
30
30
 
31
31
  async def __aenter__(self) -> Self:
cq/middlewares/retry.py CHANGED
@@ -21,6 +21,9 @@ class RetryMiddleware:
21
21
  delay: float = 0,
22
22
  exceptions: Sequence[type[BaseException]] = (Exception,),
23
23
  ) -> None:
24
+ if retry < 1:
25
+ raise ValueError(f"`retry` must be at least 1, got {retry}.")
26
+
24
27
  self.__delay = delay
25
28
  self.__exceptions = tuple(exceptions)
26
29
  self.__retry = retry
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-cq
3
- Version: 0.25.0
3
+ Version: 0.25.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
@@ -32,10 +32,17 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  # python-cq
34
34
 
35
- [![PyPI - Version](https://img.shields.io/pypi/v/python-cq.svg?color=4051b5&style=for-the-badge)](https://pypi.org/project/python-cq)
36
- [![PyPI - Downloads](https://img.shields.io/pypi/dm/python-cq.svg?color=4051b5&style=for-the-badge)](https://pypistats.org/packages/python-cq)
35
+ [![PyPI - Version](https://shieldcn.dev/pypi/v/python-cq.svg?variant=secondary)](https://pypi.org/project/python-cq)
36
+ [![PyPI - Downloads](https://shieldcn.dev/pypi/dm/python-cq.svg?variant=secondary)](https://pypistats.org/packages/python-cq)
37
37
 
38
- **python-cq** is an async-first Python library for organizing code around CQRS. It separates reads (queries), writes (commands), and notifications (events) into dedicated message buses, and lets you plug in any dependency injection framework behind a small protocol.
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 | Handlers | Returns |
53
- |-----------|---------------------------------------|-----------------------|--------------------------|
54
- | `Command` | Change the state of the system | Exactly one | The handler's return value |
55
- | `Query` | Read state without side effects | Exactly one | The handler's return value |
56
- | `Event` | Notify that something has happened | Zero, one, or many | Nothing |
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 mean it should be used as a query. Keep intent clear.
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 its first `handle` parameter. The bus is resolved by the DI container and dispatched to that handler.
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
 
@@ -3,8 +3,8 @@ cq/exceptions.py,sha256=lmIpWDbPPwdS9bAAKxUvampA1KKmB706wWaB2mKZyPc,111
3
3
  cq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cq/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cq/_core/message.py,sha256=uyFgiFCm3kVhWFzJ2gBtWNub_cgfCdyXXSJirBW05OQ,286
6
- cq/_core/middleware.py,sha256=EKrAXCbDEZmGn9JTGpL06Nd0DflTTFbDB1Z7Ja3CqmI,3710
7
- cq/_core/related_events.py,sha256=vPltQdmQjnfb3eOB2lO6dxqjJEsGOjOyCNj7_2ehNAk,1389
6
+ cq/_core/middleware.py,sha256=cckYp7gTsCFH76-DnT2yOTv8QPYH2IlGWtpmDuDcupo,3706
7
+ cq/_core/related_events.py,sha256=msJ0J-ydtxJ5My8qPLhMIFuCCvzbB_WBDD0CTwOEC-0,1369
8
8
  cq/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  cq/_core/common/typing.py,sha256=xtXsqzyATZ0LTVfjB-J4sB6dPKHKrpXqd1bZIkwVljA,504
10
10
  cq/_core/queuing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -28,8 +28,8 @@ cq/ext/injection.py,sha256=DLdALqRYaJ80wWEcw2wbZRUrNMiZVGg0AdgYIt5FrP8,3150
28
28
  cq/middlewares/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  cq/middlewares/contextlib.py,sha256=_kwbXteGADGuzD-o2pPoPWOeTKiXcPCNuDmpG-f-YnY,875
30
30
  cq/middlewares/exc.py,sha256=ecAMoksj6B4osySSZ9g_cBR35_tiABQdpII-Drxkz44,1604
31
- cq/middlewares/retry.py,sha256=L6X_Wa2TZ0IYNXmi8oexTXQyM_lkjJt5Ew4D-2VzwBY,957
32
- python_cq-0.25.0.dist-info/METADATA,sha256=E360s2Dd_Bgp3ApM1TdTLcxS0hM8GWtbsIeE5vLRrVk,4562
33
- python_cq-0.25.0.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
34
- python_cq-0.25.0.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
35
- python_cq-0.25.0.dist-info/RECORD,,
31
+ cq/middlewares/retry.py,sha256=WkpMYfAVUR8bnZ1NejX8zeKuPtfCIJlDEj8r4ZkqGBU,1054
32
+ python_cq-0.25.2.dist-info/METADATA,sha256=vFbIJUJ02nNrYB8Wx4CbZwwC5OSvPm4Y-tel5ytHeY0,5032
33
+ python_cq-0.25.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
34
+ python_cq-0.25.2.dist-info/licenses/LICENSE,sha256=oC77BOa9kaaQni5rW-Z-ytz3E5h4EVg248BHg9UFgyg,1063
35
+ python_cq-0.25.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.3
2
+ Generator: hatchling 1.33.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any