python-cq 0.19.0__tar.gz → 0.21.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 (34) hide show
  1. {python_cq-0.19.0 → python_cq-0.21.0}/PKG-INFO +1 -1
  2. {python_cq-0.19.0 → python_cq-0.21.0}/cq/__init__.py +14 -15
  3. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/cq.py +13 -0
  4. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/dispatchers/abc.py +10 -9
  5. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/dispatchers/bus.py +3 -3
  6. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/dispatchers/pipe.py +1 -1
  7. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/handler.py +14 -1
  8. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/middleware.py +0 -15
  9. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/pump.py +8 -8
  10. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/queues/abc.py +7 -6
  11. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/queues/memory.py +5 -4
  12. {python_cq-0.19.0 → python_cq-0.21.0}/pyproject.toml +1 -1
  13. {python_cq-0.19.0 → python_cq-0.21.0}/.gitignore +0 -0
  14. {python_cq-0.19.0 → python_cq-0.21.0}/LICENSE +0 -0
  15. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/__init__.py +0 -0
  16. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/common/__init__.py +0 -0
  17. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/common/typing.py +0 -0
  18. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/di.py +0 -0
  19. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/dispatchers/__init__.py +0 -0
  20. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/dispatchers/lazy.py +0 -0
  21. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/message.py +0 -0
  22. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/middlewares/__init__.py +0 -0
  23. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/middlewares/scope.py +0 -0
  24. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/pipetools.py +0 -0
  25. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/queues/__init__.py +0 -0
  26. {python_cq-0.19.0 → python_cq-0.21.0}/cq/_core/related_events.py +0 -0
  27. {python_cq-0.19.0 → python_cq-0.21.0}/cq/exceptions.py +0 -0
  28. {python_cq-0.19.0 → python_cq-0.21.0}/cq/ext/__init__.py +0 -0
  29. {python_cq-0.19.0 → python_cq-0.21.0}/cq/ext/injection.py +0 -0
  30. {python_cq-0.19.0 → python_cq-0.21.0}/cq/middlewares/__init__.py +0 -0
  31. {python_cq-0.19.0 → python_cq-0.21.0}/cq/middlewares/exc.py +0 -0
  32. {python_cq-0.19.0 → python_cq-0.21.0}/cq/middlewares/retry.py +0 -0
  33. {python_cq-0.19.0 → python_cq-0.21.0}/cq/py.typed +0 -0
  34. {python_cq-0.19.0 → python_cq-0.21.0}/docs/index.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-cq
3
- Version: 0.19.0
3
+ Version: 0.21.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
@@ -16,11 +16,12 @@ from ._core.message import (
16
16
  from ._core.middleware import Middleware, MiddlewareResult, resolve_handler_source
17
17
  from ._core.pipetools import ContextCommandPipeline as _ContextCommandPipeline
18
18
  from ._core.pump import Pump
19
- from ._core.queues.abc import Consumer, Producer, Queue
19
+ from ._core.queues.abc import Consumer, Delivery, Producer, Queue
20
20
  from ._core.queues.memory import MemoryQueue
21
21
  from ._core.related_events import AnyIORelatedEvents, RelatedEvents
22
22
 
23
23
  __all__ = (
24
+ "__cq__",
24
25
  "AnyCommandBus",
25
26
  "AnyIORelatedEvents",
26
27
  "Bus",
@@ -30,6 +31,7 @@ __all__ = (
30
31
  "Consumer",
31
32
  "ContextCommandPipeline",
32
33
  "ContextPipeline",
34
+ "Delivery",
33
35
  "DIAdapter",
34
36
  "Dispatcher",
35
37
  "Event",
@@ -54,30 +56,27 @@ __all__ = (
54
56
  )
55
57
 
56
58
  try:
57
- from cq.ext.injection import InjectionAdapter as _InjectionAdapter
59
+ from .ext.injection import InjectionAdapter as _InjectionAdapter
58
60
 
59
61
  except ImportError: # pragma: no cover
60
- _default = CQ(_NoDI())
62
+ __cq__ = CQ(_NoDI())
61
63
 
62
64
  else:
63
- _default = CQ(_InjectionAdapter())
65
+ __cq__ = CQ(_InjectionAdapter())
64
66
 
65
- _default.register_defaults()
67
+ __cq__.register_defaults()
66
68
 
67
- command_handler = _default.command_handler
68
- event_handler = _default.event_handler
69
- query_handler = _default.query_handler
69
+ command_handler = __cq__.command_handler
70
+ event_handler = __cq__.event_handler
71
+ query_handler = __cq__.query_handler
70
72
 
71
- new_command_bus = _default.new_command_bus
72
- new_event_bus = _default.new_event_bus
73
- new_query_bus = _default.new_query_bus
73
+ new_command_bus = __cq__.new_command_bus
74
+ new_event_bus = __cq__.new_event_bus
75
+ new_query_bus = __cq__.new_query_bus
74
76
 
75
77
 
76
78
  class ContextCommandPipeline[C: Command](_ContextCommandPipeline[C]):
77
79
  __slots__ = ()
78
80
 
79
- def __init__(self, di: DIAdapter = _default.di) -> None:
81
+ def __init__(self, di: DIAdapter = __cq__.di) -> None:
80
82
  super().__init__(di)
81
-
82
-
83
- del _default
@@ -1,3 +1,4 @@
1
+ from collections.abc import KeysView
1
2
  from typing import Any, Self
2
3
 
3
4
  from cq._core.di import DIAdapter
@@ -34,14 +35,26 @@ class CQ:
34
35
  def command_handler(self) -> HandlerDecorator[Command, Any]:
35
36
  return HandlerDecorator(self.__command_registry, self.__di)
36
37
 
38
+ @property
39
+ def command_types(self) -> KeysView[type[Command]]:
40
+ return self.__command_registry.message_types
41
+
37
42
  @property
38
43
  def event_handler(self) -> HandlerDecorator[Event, Any]:
39
44
  return HandlerDecorator(self.__event_registry, self.__di)
40
45
 
46
+ @property
47
+ def event_types(self) -> KeysView[type[Event]]:
48
+ return self.__event_registry.message_types
49
+
41
50
  @property
42
51
  def query_handler(self) -> HandlerDecorator[Query, Any]:
43
52
  return HandlerDecorator(self.__query_registry, self.__di)
44
53
 
54
+ @property
55
+ def query_types(self) -> KeysView[type[Query]]:
56
+ return self.__query_registry.message_types
57
+
45
58
  def new_command_bus(self) -> Bus[Command, Any]:
46
59
  bus = SimpleBus(self.__command_registry)
47
60
  command_middleware = CommandDispatchScopeMiddleware(self.__di)
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
2
2
  from collections.abc import Awaitable, Callable
3
3
  from typing import Protocol, Self, runtime_checkable
4
4
 
5
- from cq._core.middleware import Middleware, MiddlewareGroup, deliver_message
5
+ from cq._core.middleware import Middleware, MiddlewareGroup
6
6
 
7
7
 
8
8
  @runtime_checkable
@@ -29,16 +29,17 @@ class BaseDispatcher[I, O](Dispatcher[I, O], ABC):
29
29
  self.__middleware_group.add(*middlewares)
30
30
  return self
31
31
 
32
- async def _deliver(
32
+ async def _invoke(
33
33
  self,
34
- message: I,
35
34
  handler: Callable[[I], Awaitable[O]],
35
+ message: I,
36
36
  /,
37
37
  fail_silently: bool = False,
38
38
  ) -> O:
39
- return await deliver_message(
40
- message,
41
- handler,
42
- self.__middleware_group,
43
- fail_silently,
44
- )
39
+ try:
40
+ return await self.__middleware_group.invoke(handler, message)
41
+ except Exception:
42
+ if fail_silently:
43
+ return NotImplemented
44
+
45
+ raise
@@ -83,7 +83,7 @@ class SimpleBus[I, O](BaseBus[I, O]):
83
83
  self._trigger_listeners(message, task_group)
84
84
 
85
85
  for handler in self._handlers_from(type(message)):
86
- return await self._deliver(message, handler, handler.fail_silently)
86
+ return await self._invoke(handler, message, handler.fail_silently)
87
87
 
88
88
  return NotImplemented
89
89
 
@@ -100,8 +100,8 @@ class TaskBus[I](BaseBus[I, None]):
100
100
 
101
101
  for handler in self._handlers_from(type(message)):
102
102
  task_group.start_soon(
103
- self._deliver,
104
- message,
103
+ self._invoke,
105
104
  handler,
105
+ message,
106
106
  handler.fail_silently,
107
107
  )
@@ -140,7 +140,7 @@ class Pipe[I, O](BaseDispatcher[I, O]):
140
140
  return self
141
141
 
142
142
  async def dispatch(self, message: I, /) -> O:
143
- return await self._deliver(message, self.__steps.execute)
143
+ return await self._invoke(self.__steps.execute, message)
144
144
 
145
145
 
146
146
  class ContextPipeline[I]:
@@ -1,6 +1,6 @@
1
1
  from abc import abstractmethod
2
2
  from collections import defaultdict
3
- from collections.abc import Awaitable, Callable, Iterator
3
+ from collections.abc import Awaitable, Callable, Iterator, KeysView
4
4
  from dataclasses import dataclass, field
5
5
  from functools import partial
6
6
  from inspect import Parameter, isclass, unwrap
@@ -49,6 +49,11 @@ class HandleFunction[**P, T]:
49
49
  class HandlerRegistry[I, O](Protocol):
50
50
  __slots__ = ()
51
51
 
52
+ @property
53
+ @abstractmethod
54
+ def message_types(self) -> KeysView[type[I]]:
55
+ raise NotImplementedError
56
+
52
57
  @abstractmethod
53
58
  def handlers_from(self, message_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
54
59
  raise NotImplementedError
@@ -71,6 +76,10 @@ class MultipleHandlerRegistry[I, O](HandlerRegistry[I, O]):
71
76
  init=False,
72
77
  )
73
78
 
79
+ @property
80
+ def message_types(self) -> KeysView[type[I]]:
81
+ return self.__values.keys()
82
+
74
83
  def handlers_from(self, message_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
75
84
  for key_type in _iter_key_types(message_type):
76
85
  yield from self.__values.get(key_type, ())
@@ -97,6 +106,10 @@ class SingleHandlerRegistry[I, O](HandlerRegistry[I, O]):
97
106
  init=False,
98
107
  )
99
108
 
109
+ @property
110
+ def message_types(self) -> KeysView[type[I]]:
111
+ return self.__values.keys()
112
+
100
113
  def handlers_from(self, message_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
101
114
  for key_type in _iter_key_types(message_type):
102
115
  function = self.__values.get(key_type, None)
@@ -115,21 +115,6 @@ class _GeneratorMiddleware[**P, T]:
115
115
  return value
116
116
 
117
117
 
118
- async def deliver_message[I, O](
119
- message: I,
120
- handler: Callable[[I], Awaitable[O]],
121
- middleware_group: MiddlewareGroup[[I], O],
122
- fail_silently: bool = False,
123
- ) -> O:
124
- try:
125
- return await middleware_group.invoke(handler, message)
126
- except Exception:
127
- if fail_silently:
128
- return NotImplemented
129
-
130
- raise
131
-
132
-
133
118
  def _is_gen_middleware[**P, T](
134
119
  middleware: Middleware[P, T],
135
120
  ) -> TypeGuard[GeneratorMiddleware[P, T]]:
@@ -5,7 +5,7 @@ from typing import Any, Self
5
5
 
6
6
  import anyio
7
7
 
8
- from cq._core.middleware import Middleware, MiddlewareGroup, deliver_message
8
+ from cq._core.middleware import Middleware, MiddlewareGroup
9
9
  from cq._core.queues.abc import Consumer
10
10
 
11
11
 
@@ -24,13 +24,13 @@ class Pump[T]:
24
24
  return self
25
25
 
26
26
  async def drain(self) -> None:
27
- async for message in self.consumer:
28
- await deliver_message(
29
- message,
30
- self.dispatcher,
31
- self.__middleware_group,
32
- self.fail_silently,
33
- )
27
+ async for delivery in self.consumer:
28
+ try:
29
+ async with delivery as message:
30
+ await self.__middleware_group.invoke(self.dispatcher, message)
31
+ except Exception:
32
+ if not self.fail_silently:
33
+ raise
34
34
 
35
35
  @asynccontextmanager
36
36
  async def draining(
@@ -1,6 +1,6 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import AsyncIterator
3
- from typing import Protocol, runtime_checkable
2
+ from collections.abc import AsyncIterable
3
+ from typing import AsyncContextManager, Protocol, runtime_checkable
4
4
 
5
5
 
6
6
  @runtime_checkable
@@ -16,12 +16,13 @@ class Producer[T](Protocol):
16
16
 
17
17
 
18
18
  @runtime_checkable
19
- class Consumer[T](Protocol):
19
+ class Delivery[T](AsyncContextManager[T], Protocol):
20
20
  __slots__ = ()
21
21
 
22
- @abstractmethod
23
- def __aiter__(self) -> AsyncIterator[T]:
24
- raise NotImplementedError
22
+
23
+ @runtime_checkable
24
+ class Consumer[T](AsyncIterable[Delivery[T]], Protocol):
25
+ __slots__ = ()
25
26
 
26
27
 
27
28
  @runtime_checkable
@@ -1,5 +1,5 @@
1
1
  from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
2
- from contextlib import asynccontextmanager
2
+ from contextlib import asynccontextmanager, nullcontext
3
3
  from types import TracebackType
4
4
  from typing import Any, Self
5
5
 
@@ -8,7 +8,7 @@ from anyio.abc import ObjectReceiveStream, ObjectSendStream
8
8
 
9
9
  from cq._core.middleware import Middleware
10
10
  from cq._core.pump import Pump
11
- from cq._core.queues.abc import Queue
11
+ from cq._core.queues.abc import Delivery, Queue
12
12
 
13
13
 
14
14
  class MemoryQueue[T](Queue[T]):
@@ -31,8 +31,9 @@ class MemoryQueue[T](Queue[T]):
31
31
  ) -> None:
32
32
  await self.close()
33
33
 
34
- def __aiter__(self) -> AsyncIterator[T]:
35
- return aiter(self.__consumer)
34
+ async def __aiter__(self) -> AsyncIterator[Delivery[T]]:
35
+ async for message in self.__consumer:
36
+ yield nullcontext(message)
36
37
 
37
38
  async def close(self) -> None:
38
39
  await self.__producer.aclose()
@@ -20,7 +20,7 @@ test = [
20
20
 
21
21
  [project]
22
22
  name = "python-cq"
23
- version = "0.19.0"
23
+ version = "0.21.0"
24
24
  description = "CQRS library for async Python projects."
25
25
  license = "MIT"
26
26
  license-files = ["LICENSE"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes