python-cq 0.1.3__tar.gz → 0.2.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.1.3 → python_cq-0.2.0}/PKG-INFO +3 -1
- {python_cq-0.1.3 → python_cq-0.2.0}/README.md +1 -0
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/__init__.py +11 -2
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/_core/command.py +2 -1
- python_cq-0.2.0/cq/_core/dispatcher/base.py +53 -0
- {python_cq-0.1.3/cq/_core → python_cq-0.2.0/cq/_core/dispatcher}/bus.py +13 -40
- python_cq-0.2.0/cq/_core/dispatcher/pipe.py +52 -0
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/_core/event.py +1 -1
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/_core/middleware.py +3 -6
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/_core/query.py +1 -1
- python_cq-0.2.0/cq/py.typed +0 -0
- {python_cq-0.1.3 → python_cq-0.2.0}/pyproject.toml +1 -2
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/_core/__init__.py +0 -0
- {python_cq-0.1.3/cq/middlewares → python_cq-0.2.0/cq/_core/dispatcher}/__init__.py +0 -0
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/_core/dto.py +0 -0
- /python_cq-0.1.3/cq/py.typed → /python_cq-0.2.0/cq/middlewares/__init__.py +0 -0
- {python_cq-0.1.3 → python_cq-0.2.0}/cq/middlewares/retry.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-cq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Lightweight CQRS library.
|
|
5
5
|
Home-page: https://github.com/100nm/python-cq
|
|
6
6
|
License: MIT
|
|
@@ -15,6 +15,7 @@ Classifier: Operating System :: OS Independent
|
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
20
|
Classifier: Topic :: Software Development :: Libraries
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
@@ -48,5 +49,6 @@ pip install python-cq
|
|
|
48
49
|
## Resources
|
|
49
50
|
|
|
50
51
|
* [**Writing Application Layer**](https://github.com/100nm/python-cq/tree/prod/documentation/writing-application-layer.md)
|
|
52
|
+
* [**Pipeline**](https://github.com/100nm/python-cq/tree/prod/documentation/pipeline.md)
|
|
51
53
|
* [**FastAPI Example**](https://github.com/100nm/python-cq/tree/prod/documentation/fastapi-example.md)
|
|
52
54
|
|
|
@@ -21,4 +21,5 @@ pip install python-cq
|
|
|
21
21
|
## Resources
|
|
22
22
|
|
|
23
23
|
* [**Writing Application Layer**](https://github.com/100nm/python-cq/tree/prod/documentation/writing-application-layer.md)
|
|
24
|
+
* [**Pipeline**](https://github.com/100nm/python-cq/tree/prod/documentation/pipeline.md)
|
|
24
25
|
* [**FastAPI Example**](https://github.com/100nm/python-cq/tree/prod/documentation/fastapi-example.md)
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
from ._core.
|
|
2
|
-
|
|
1
|
+
from ._core.command import (
|
|
2
|
+
AnyCommandBus,
|
|
3
|
+
Command,
|
|
4
|
+
CommandBus,
|
|
5
|
+
command_handler,
|
|
6
|
+
find_command_bus,
|
|
7
|
+
)
|
|
8
|
+
from ._core.dispatcher.bus import Bus
|
|
9
|
+
from ._core.dispatcher.pipe import Pipe
|
|
3
10
|
from ._core.dto import DTO
|
|
4
11
|
from ._core.event import Event, EventBus, event_handler, find_event_bus
|
|
5
12
|
from ._core.middleware import Middleware, MiddlewareResult
|
|
6
13
|
from ._core.query import Query, QueryBus, find_query_bus, query_handler
|
|
7
14
|
|
|
8
15
|
__all__ = (
|
|
16
|
+
"AnyCommandBus",
|
|
9
17
|
"Bus",
|
|
10
18
|
"Command",
|
|
11
19
|
"CommandBus",
|
|
@@ -14,6 +22,7 @@ __all__ = (
|
|
|
14
22
|
"EventBus",
|
|
15
23
|
"Middleware",
|
|
16
24
|
"MiddlewareResult",
|
|
25
|
+
"Pipe",
|
|
17
26
|
"Query",
|
|
18
27
|
"QueryBus",
|
|
19
28
|
"command_handler",
|
|
@@ -3,7 +3,7 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
import injection
|
|
5
5
|
|
|
6
|
-
from cq._core.bus import Bus, SimpleBus, SubscriberDecorator
|
|
6
|
+
from cq._core.dispatcher.bus import Bus, SimpleBus, SubscriberDecorator
|
|
7
7
|
from cq._core.dto import DTO
|
|
8
8
|
|
|
9
9
|
|
|
@@ -12,6 +12,7 @@ class Command(DTO, ABC):
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
type CommandBus[T] = Bus[Command, T]
|
|
15
|
+
AnyCommandBus = CommandBus[Any]
|
|
15
16
|
command_handler: SubscriberDecorator[Command, Any] = SubscriberDecorator(CommandBus)
|
|
16
17
|
|
|
17
18
|
injection.set_constant(SimpleBus(), CommandBus, alias=True)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Awaitable
|
|
4
|
+
from typing import Callable, Protocol, Self, runtime_checkable
|
|
5
|
+
|
|
6
|
+
from cq._core.middleware import Middleware, MiddlewareGroup
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class Dispatcher[I, O](Protocol):
|
|
11
|
+
__slots__ = ()
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def dispatch(self, input_value: I, /) -> O:
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def dispatch_no_wait(self, first_input_value: I, /, *input_values: I) -> None:
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def add_middlewares(self, *middlewares: Middleware[[I], O]) -> Self:
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseDispatcher[I, O](Dispatcher[I, O], ABC):
|
|
27
|
+
__slots__ = ("__middleware_group",)
|
|
28
|
+
|
|
29
|
+
__middleware_group: MiddlewareGroup[[I], O]
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self.__middleware_group = MiddlewareGroup()
|
|
33
|
+
|
|
34
|
+
def dispatch_no_wait(self, first_input_value: I, /, *input_values: I) -> None:
|
|
35
|
+
asyncio.gather(
|
|
36
|
+
*(
|
|
37
|
+
self.dispatch(input_value)
|
|
38
|
+
for input_value in (first_input_value, *input_values)
|
|
39
|
+
),
|
|
40
|
+
return_exceptions=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def add_middlewares(self, *middlewares: Middleware[[I], O]) -> Self:
|
|
44
|
+
self.__middleware_group.add(*middlewares)
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
async def _invoke_with_middlewares(
|
|
48
|
+
self,
|
|
49
|
+
handler: Callable[[I], Awaitable[O]],
|
|
50
|
+
input_value: I,
|
|
51
|
+
/,
|
|
52
|
+
) -> O:
|
|
53
|
+
return await self.__middleware_group.invoke(handler, input_value)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from abc import
|
|
2
|
+
from abc import abstractmethod
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from dataclasses import dataclass, field
|
|
@@ -9,7 +9,7 @@ from typing import Protocol, Self, TypeAliasType, runtime_checkable
|
|
|
9
9
|
|
|
10
10
|
import injection
|
|
11
11
|
|
|
12
|
-
from cq._core.
|
|
12
|
+
from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
|
|
13
13
|
|
|
14
14
|
type HandlerType[**P, T] = type[Handler[P, T]]
|
|
15
15
|
type HandlerFactory[**P, T] = Callable[..., Handler[P, T]]
|
|
@@ -27,30 +27,13 @@ class Handler[**P, T](Protocol):
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@runtime_checkable
|
|
30
|
-
class Bus[I, O](Protocol):
|
|
30
|
+
class Bus[I, O](Dispatcher[I, O], Protocol):
|
|
31
31
|
__slots__ = ()
|
|
32
32
|
|
|
33
|
-
@abstractmethod
|
|
34
|
-
async def dispatch(self, input_value: I, /) -> O:
|
|
35
|
-
raise NotImplementedError
|
|
36
|
-
|
|
37
|
-
def dispatch_no_wait(self, first_input_value: I, /, *input_values: I) -> None:
|
|
38
|
-
asyncio.gather(
|
|
39
|
-
*(
|
|
40
|
-
self.dispatch(input_value)
|
|
41
|
-
for input_value in (first_input_value, *input_values)
|
|
42
|
-
),
|
|
43
|
-
return_exceptions=True,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
33
|
@abstractmethod
|
|
47
34
|
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
48
35
|
raise NotImplementedError
|
|
49
36
|
|
|
50
|
-
@abstractmethod
|
|
51
|
-
def add_middlewares(self, *middlewares: Middleware[[I], O]) -> Self:
|
|
52
|
-
raise NotImplementedError
|
|
53
|
-
|
|
54
37
|
|
|
55
38
|
@dataclass(eq=False, frozen=True, slots=True)
|
|
56
39
|
class SubscriberDecorator[I, O]:
|
|
@@ -81,23 +64,7 @@ class SubscriberDecorator[I, O]:
|
|
|
81
64
|
return self.injection_module.find_instance(self.bus_type)
|
|
82
65
|
|
|
83
66
|
|
|
84
|
-
class
|
|
85
|
-
__slots__ = ("__middleware_group",)
|
|
86
|
-
|
|
87
|
-
__middleware_group: MiddlewareGroup[[I], O]
|
|
88
|
-
|
|
89
|
-
def __init__(self) -> None:
|
|
90
|
-
self.__middleware_group = MiddlewareGroup()
|
|
91
|
-
|
|
92
|
-
def add_middlewares(self, *middlewares: Middleware[[I], O]) -> Self:
|
|
93
|
-
self.__middleware_group.add(*middlewares)
|
|
94
|
-
return self
|
|
95
|
-
|
|
96
|
-
async def _invoke(self, handler: Handler[[I], O], input_value: I, /) -> O:
|
|
97
|
-
return await self.__middleware_group.invoke(handler.handle, input_value)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class SimpleBus[I, O](_BaseBus[I, O]):
|
|
67
|
+
class SimpleBus[I, O](BaseDispatcher[I, O], Bus[I, O]):
|
|
101
68
|
__slots__ = ("__handlers",)
|
|
102
69
|
|
|
103
70
|
__handlers: dict[type[I], HandlerFactory[[I], O]]
|
|
@@ -114,7 +81,10 @@ class SimpleBus[I, O](_BaseBus[I, O]):
|
|
|
114
81
|
except KeyError:
|
|
115
82
|
return NotImplemented
|
|
116
83
|
|
|
117
|
-
return await self.
|
|
84
|
+
return await self._invoke_with_middlewares(
|
|
85
|
+
handler_factory().handle,
|
|
86
|
+
input_value,
|
|
87
|
+
)
|
|
118
88
|
|
|
119
89
|
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
120
90
|
if input_type in self.__handlers:
|
|
@@ -126,7 +96,7 @@ class SimpleBus[I, O](_BaseBus[I, O]):
|
|
|
126
96
|
return self
|
|
127
97
|
|
|
128
98
|
|
|
129
|
-
class TaskBus[I](
|
|
99
|
+
class TaskBus[I](BaseDispatcher[I, None], Bus[I, None]):
|
|
130
100
|
__slots__ = ("__handlers",)
|
|
131
101
|
|
|
132
102
|
__handlers: dict[type[I], list[HandlerFactory[[I], None]]]
|
|
@@ -143,7 +113,10 @@ class TaskBus[I](_BaseBus[I, None]):
|
|
|
143
113
|
|
|
144
114
|
await asyncio.gather(
|
|
145
115
|
*(
|
|
146
|
-
self.
|
|
116
|
+
self._invoke_with_middlewares(
|
|
117
|
+
handler_factory().handle,
|
|
118
|
+
input_value,
|
|
119
|
+
)
|
|
147
120
|
for handler_factory in handler_factories
|
|
148
121
|
)
|
|
149
122
|
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Awaitable
|
|
4
|
+
|
|
5
|
+
from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
|
|
6
|
+
|
|
7
|
+
type PipeConverter[I, O] = Callable[[O], Awaitable[I]]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
11
|
+
class PipeStep[I, O]:
|
|
12
|
+
converter: PipeConverter[I, O]
|
|
13
|
+
dispatcher: Dispatcher[I, Any] | None = field(default=None)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Pipe[I, O](BaseDispatcher[I, O]):
|
|
17
|
+
__slots__ = ("__dispatcher", "__steps")
|
|
18
|
+
|
|
19
|
+
__dispatcher: Dispatcher[Any, Any]
|
|
20
|
+
__steps: list[PipeStep[Any, Any]]
|
|
21
|
+
|
|
22
|
+
def __init__(self, dispatcher: Dispatcher[Any, Any]) -> None:
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.__dispatcher = dispatcher
|
|
25
|
+
self.__steps = []
|
|
26
|
+
|
|
27
|
+
def step[T]( # type: ignore[no-untyped-def]
|
|
28
|
+
self,
|
|
29
|
+
wrapped: PipeConverter[T, Any] | None = None,
|
|
30
|
+
/,
|
|
31
|
+
*,
|
|
32
|
+
dispatcher: Dispatcher[T, Any] | None = None,
|
|
33
|
+
):
|
|
34
|
+
def decorator(wp): # type: ignore[no-untyped-def]
|
|
35
|
+
step = PipeStep(wp, dispatcher)
|
|
36
|
+
self.__steps.append(step)
|
|
37
|
+
return wp
|
|
38
|
+
|
|
39
|
+
return decorator(wrapped) if wrapped else decorator
|
|
40
|
+
|
|
41
|
+
async def dispatch(self, input_value: I, /) -> O:
|
|
42
|
+
return await self._invoke_with_middlewares(self.__execute, input_value)
|
|
43
|
+
|
|
44
|
+
async def __execute(self, input_value: I) -> O:
|
|
45
|
+
dispatcher = self.__dispatcher
|
|
46
|
+
|
|
47
|
+
for step in self.__steps:
|
|
48
|
+
output_value = await dispatcher.dispatch(input_value)
|
|
49
|
+
input_value = await step.converter(output_value)
|
|
50
|
+
dispatcher = step.dispatcher or self.__dispatcher
|
|
51
|
+
|
|
52
|
+
return await dispatcher.dispatch(input_value)
|
|
@@ -6,13 +6,9 @@ type MiddlewareResult[T] = AsyncGenerator[None, T]
|
|
|
6
6
|
type Middleware[**P, T] = Callable[P, MiddlewareResult[T]]
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@dataclass(eq=False, frozen=True, slots=True)
|
|
9
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
10
10
|
class MiddlewareGroup[**P, T]:
|
|
11
|
-
__middlewares: list[Middleware[P, T]] = field(
|
|
12
|
-
default_factory=list,
|
|
13
|
-
init=False,
|
|
14
|
-
repr=False,
|
|
15
|
-
)
|
|
11
|
+
__middlewares: list[Middleware[P, T]] = field(default_factory=list, init=False)
|
|
16
12
|
|
|
17
13
|
@property
|
|
18
14
|
def __stack(self) -> Iterator[Middleware[P, T]]:
|
|
@@ -51,6 +47,7 @@ class MiddlewareGroup[**P, T]:
|
|
|
51
47
|
await generator.athrow(exc)
|
|
52
48
|
else:
|
|
53
49
|
await generator.asend(value)
|
|
50
|
+
break
|
|
54
51
|
|
|
55
52
|
except StopAsyncIteration:
|
|
56
53
|
...
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-cq"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Lightweight CQRS library."
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["remimd"]
|
|
@@ -51,7 +51,6 @@ check_untyped_defs = true
|
|
|
51
51
|
disallow_any_generics = true
|
|
52
52
|
disallow_subclassing_any = true
|
|
53
53
|
disallow_untyped_defs = true
|
|
54
|
-
enable_incomplete_feature = ["NewGenericSyntax"]
|
|
55
54
|
follow_imports = "silent"
|
|
56
55
|
no_implicit_reexport = true
|
|
57
56
|
plugins = ["pydantic.mypy"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|