python-cq 0.4.0__tar.gz → 0.5.1__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.4.0 → python_cq-0.5.1}/PKG-INFO +1 -1
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/__init__.py +6 -6
- python_cq-0.5.1/cq/_core/dispatcher/bus.py +93 -0
- python_cq-0.5.1/cq/_core/handler.py +111 -0
- python_cq-0.5.1/cq/_core/message.py +78 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/related_events.py +3 -6
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/middlewares/retry.py +4 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/middlewares/scope.py +2 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/pyproject.toml +2 -2
- python_cq-0.4.0/cq/_core/dispatcher/bus.py +0 -165
- python_cq-0.4.0/cq/_core/message.py +0 -73
- {python_cq-0.4.0 → python_cq-0.5.1}/.gitignore +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/README.md +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/__init__.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/dispatcher/__init__.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/dispatcher/base.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/dispatcher/pipe.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/dto.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/middleware.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/_core/scope.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/exceptions.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/middlewares/__init__.py +0 -0
- {python_cq-0.4.0 → python_cq-0.5.1}/cq/py.typed +0 -0
|
@@ -12,9 +12,9 @@ from ._core.message import (
|
|
|
12
12
|
QueryBus,
|
|
13
13
|
command_handler,
|
|
14
14
|
event_handler,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
new_command_bus,
|
|
16
|
+
new_event_bus,
|
|
17
|
+
new_query_bus,
|
|
18
18
|
query_handler,
|
|
19
19
|
)
|
|
20
20
|
from ._core.middleware import Middleware, MiddlewareResult
|
|
@@ -39,8 +39,8 @@ __all__ = (
|
|
|
39
39
|
"RelatedEvents",
|
|
40
40
|
"command_handler",
|
|
41
41
|
"event_handler",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
42
|
+
"new_command_bus",
|
|
43
|
+
"new_event_bus",
|
|
44
|
+
"new_query_bus",
|
|
45
45
|
"query_handler",
|
|
46
46
|
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import Awaitable, Callable, Iterator
|
|
3
|
+
from typing import Any, Protocol, Self, runtime_checkable
|
|
4
|
+
|
|
5
|
+
import anyio
|
|
6
|
+
from anyio.abc import TaskGroup
|
|
7
|
+
|
|
8
|
+
from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
|
|
9
|
+
from cq._core.handler import (
|
|
10
|
+
HandlerFactory,
|
|
11
|
+
HandlerManager,
|
|
12
|
+
MultipleHandlerManager,
|
|
13
|
+
SingleHandlerManager,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type Listener[T] = Callable[[T], Awaitable[Any]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class Bus[I, O](Dispatcher[I, O], Protocol):
|
|
21
|
+
__slots__ = ()
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def add_listeners(self, *listeners: Listener[I]) -> Self:
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseBus[I, O](BaseDispatcher[I, O], Bus[I, O], ABC):
|
|
33
|
+
__slots__ = ("__listeners", "__manager")
|
|
34
|
+
|
|
35
|
+
__listeners: list[Listener[I]]
|
|
36
|
+
__manager: HandlerManager[I, O]
|
|
37
|
+
|
|
38
|
+
def __init__(self, manager: HandlerManager[I, O]) -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
self.__listeners = []
|
|
41
|
+
self.__manager = manager
|
|
42
|
+
|
|
43
|
+
def add_listeners(self, *listeners: Listener[I]) -> Self:
|
|
44
|
+
self.__listeners.extend(listeners)
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
48
|
+
self.__manager.subscribe(input_type, factory)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def _handlers_from(
|
|
52
|
+
self,
|
|
53
|
+
input_type: type[I],
|
|
54
|
+
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
55
|
+
return self.__manager.handlers_from(input_type)
|
|
56
|
+
|
|
57
|
+
def _trigger_listeners(self, input_value: I, /, task_group: TaskGroup) -> None:
|
|
58
|
+
for listener in self.__listeners:
|
|
59
|
+
task_group.start_soon(listener, input_value)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SimpleBus[I, O](BaseBus[I, O]):
|
|
63
|
+
__slots__ = ()
|
|
64
|
+
|
|
65
|
+
def __init__(self, manager: HandlerManager[I, O] | None = None) -> None:
|
|
66
|
+
super().__init__(manager or SingleHandlerManager())
|
|
67
|
+
|
|
68
|
+
async def dispatch(self, input_value: I, /) -> O:
|
|
69
|
+
async with anyio.create_task_group() as task_group:
|
|
70
|
+
self._trigger_listeners(input_value, task_group)
|
|
71
|
+
|
|
72
|
+
for handler in self._handlers_from(type(input_value)):
|
|
73
|
+
return await self._invoke_with_middlewares(handler, input_value)
|
|
74
|
+
|
|
75
|
+
return NotImplemented
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TaskBus[I](BaseBus[I, None]):
|
|
79
|
+
__slots__ = ()
|
|
80
|
+
|
|
81
|
+
def __init__(self, manager: HandlerManager[I, None] | None = None) -> None:
|
|
82
|
+
super().__init__(manager or MultipleHandlerManager())
|
|
83
|
+
|
|
84
|
+
async def dispatch(self, input_value: I, /) -> None:
|
|
85
|
+
async with anyio.create_task_group() as task_group:
|
|
86
|
+
self._trigger_listeners(input_value, task_group)
|
|
87
|
+
|
|
88
|
+
for handler in self._handlers_from(type(input_value)):
|
|
89
|
+
task_group.start_soon(
|
|
90
|
+
self._invoke_with_middlewares,
|
|
91
|
+
handler,
|
|
92
|
+
input_value,
|
|
93
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from collections.abc import Awaitable, Callable, Iterator
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from functools import partial
|
|
6
|
+
from inspect import getmro, isclass
|
|
7
|
+
from typing import Any, Protocol, Self, runtime_checkable
|
|
8
|
+
|
|
9
|
+
import injection
|
|
10
|
+
|
|
11
|
+
type HandlerType[**P, T] = type[Handler[P, T]]
|
|
12
|
+
type HandlerFactory[**P, T] = Callable[..., Awaitable[Handler[P, T]]]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class Handler[**P, T](Protocol):
|
|
17
|
+
__slots__ = ()
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
async def handle(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@runtime_checkable
|
|
25
|
+
class HandlerManager[I, O](Protocol):
|
|
26
|
+
__slots__ = ()
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def handlers_from(
|
|
30
|
+
self,
|
|
31
|
+
input_type: type[I],
|
|
32
|
+
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
41
|
+
class MultipleHandlerManager[I, O](HandlerManager[I, O]):
|
|
42
|
+
__factories: dict[type[I], list[HandlerFactory[[I], O]]] = field(
|
|
43
|
+
default_factory=partial(defaultdict, list),
|
|
44
|
+
init=False,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def handlers_from(
|
|
48
|
+
self,
|
|
49
|
+
input_type: type[I],
|
|
50
|
+
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
51
|
+
for it in getmro(input_type):
|
|
52
|
+
for factory in self.__factories.get(it, ()):
|
|
53
|
+
yield _make_handle_function(factory)
|
|
54
|
+
|
|
55
|
+
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
56
|
+
self.__factories[input_type].append(factory)
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
61
|
+
class SingleHandlerManager[I, O](HandlerManager[I, O]):
|
|
62
|
+
__factories: dict[type[I], HandlerFactory[[I], O]] = field(
|
|
63
|
+
default_factory=dict,
|
|
64
|
+
init=False,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def handlers_from(
|
|
68
|
+
self,
|
|
69
|
+
input_type: type[I],
|
|
70
|
+
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
71
|
+
for it in getmro(input_type):
|
|
72
|
+
factory = self.__factories.get(it, None)
|
|
73
|
+
if factory is not None:
|
|
74
|
+
yield _make_handle_function(factory)
|
|
75
|
+
|
|
76
|
+
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
77
|
+
if input_type in self.__factories:
|
|
78
|
+
raise RuntimeError(
|
|
79
|
+
f"A handler is already registered for the input type: `{input_type}`."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.__factories[input_type] = factory
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
87
|
+
class HandlerDecorator[I, O]:
|
|
88
|
+
manager: HandlerManager[I, O]
|
|
89
|
+
injection_module: injection.Module = field(default_factory=injection.mod)
|
|
90
|
+
|
|
91
|
+
def __call__(self, input_type: type[I], /) -> Any:
|
|
92
|
+
def decorator(wrapped: type[Handler[[I], O]]) -> type[Handler[[I], O]]:
|
|
93
|
+
if not isclass(wrapped) or not issubclass(wrapped, Handler):
|
|
94
|
+
raise TypeError(f"`{wrapped}` isn't a valid handler.")
|
|
95
|
+
|
|
96
|
+
factory = self.injection_module.make_async_factory(wrapped)
|
|
97
|
+
self.manager.subscribe(input_type, factory)
|
|
98
|
+
return wrapped
|
|
99
|
+
|
|
100
|
+
return decorator
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _make_handle_function[I, O](
|
|
104
|
+
factory: HandlerFactory[[I], O],
|
|
105
|
+
) -> Callable[[I], Awaitable[O]]:
|
|
106
|
+
return partial(__handle, factory=factory)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def __handle[I, O](input_value: I, factory: HandlerFactory[[I], O]) -> O:
|
|
110
|
+
handler = await factory()
|
|
111
|
+
return await handler.handle(input_value)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import injection
|
|
5
|
+
|
|
6
|
+
from cq._core.dispatcher.bus import Bus, SimpleBus, TaskBus
|
|
7
|
+
from cq._core.dto import DTO
|
|
8
|
+
from cq._core.handler import (
|
|
9
|
+
HandlerDecorator,
|
|
10
|
+
MultipleHandlerManager,
|
|
11
|
+
SingleHandlerManager,
|
|
12
|
+
)
|
|
13
|
+
from cq._core.scope import CQScope
|
|
14
|
+
from cq.middlewares.scope import InjectionScopeMiddleware
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Message(DTO, ABC):
|
|
18
|
+
__slots__ = ()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Command(Message, ABC):
|
|
22
|
+
__slots__ = ()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Event(Message, ABC):
|
|
26
|
+
__slots__ = ()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Query(Message, ABC):
|
|
30
|
+
__slots__ = ()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
type CommandBus[T] = Bus[Command, T]
|
|
34
|
+
type EventBus = Bus[Event, None]
|
|
35
|
+
type QueryBus[T] = Bus[Query, T]
|
|
36
|
+
|
|
37
|
+
AnyCommandBus = CommandBus[Any]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
command_handler: HandlerDecorator[Command, Any] = HandlerDecorator(
|
|
41
|
+
SingleHandlerManager(),
|
|
42
|
+
)
|
|
43
|
+
event_handler: HandlerDecorator[Event, None] = HandlerDecorator(
|
|
44
|
+
MultipleHandlerManager(),
|
|
45
|
+
)
|
|
46
|
+
query_handler: HandlerDecorator[Query, Any] = HandlerDecorator(
|
|
47
|
+
SingleHandlerManager(),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@injection.singleton(
|
|
52
|
+
on=CommandBus,
|
|
53
|
+
ignore_type_hint=True, # type: ignore[call-arg]
|
|
54
|
+
inject=False,
|
|
55
|
+
mode="fallback",
|
|
56
|
+
)
|
|
57
|
+
def new_command_bus[T]() -> CommandBus[T]:
|
|
58
|
+
bus = SimpleBus(command_handler.manager)
|
|
59
|
+
bus.add_middlewares(InjectionScopeMiddleware(CQScope.ON_COMMAND))
|
|
60
|
+
return bus
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@injection.singleton(
|
|
64
|
+
inject=False,
|
|
65
|
+
mode="fallback",
|
|
66
|
+
)
|
|
67
|
+
def new_event_bus() -> EventBus:
|
|
68
|
+
return TaskBus(event_handler.manager)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@injection.singleton(
|
|
72
|
+
on=QueryBus,
|
|
73
|
+
ignore_type_hint=True, # type: ignore[call-arg]
|
|
74
|
+
inject=False,
|
|
75
|
+
mode="fallback",
|
|
76
|
+
)
|
|
77
|
+
def new_query_bus[T]() -> QueryBus[T]:
|
|
78
|
+
return SimpleBus(query_handler.manager)
|
|
@@ -19,7 +19,7 @@ class RelatedEvents(Protocol):
|
|
|
19
19
|
raise NotImplementedError
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
@dataclass(frozen=True, slots=True)
|
|
22
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
23
23
|
class _RelatedEvents(RelatedEvents):
|
|
24
24
|
items: list[Event] = field(default_factory=list)
|
|
25
25
|
|
|
@@ -27,8 +27,8 @@ class _RelatedEvents(RelatedEvents):
|
|
|
27
27
|
self.items.extend(events)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
@injection.scoped(CQScope.ON_COMMAND)
|
|
31
|
-
async def
|
|
30
|
+
@injection.scoped(CQScope.ON_COMMAND, mode="fallback")
|
|
31
|
+
async def related_events_recipe(event_bus: EventBus) -> AsyncIterator[RelatedEvents]:
|
|
32
32
|
yield (instance := _RelatedEvents())
|
|
33
33
|
events = instance.items
|
|
34
34
|
|
|
@@ -38,6 +38,3 @@ async def _related_events_recipe(event_bus: EventBus) -> AsyncIterator[RelatedEv
|
|
|
38
38
|
async with anyio.create_task_group() as task_group:
|
|
39
39
|
for event in events:
|
|
40
40
|
task_group.start_soon(event_bus.dispatch, event)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
del _related_events_recipe
|
|
@@ -19,7 +19,7 @@ test = [
|
|
|
19
19
|
|
|
20
20
|
[project]
|
|
21
21
|
name = "python-cq"
|
|
22
|
-
version = "0.
|
|
22
|
+
version = "0.5.1"
|
|
23
23
|
description = "Lightweight CQRS library."
|
|
24
24
|
license = { text = "MIT" }
|
|
25
25
|
readme = "README.md"
|
|
@@ -102,5 +102,5 @@ extend-select = ["F", "I", "N"]
|
|
|
102
102
|
fixable = ["ALL"]
|
|
103
103
|
|
|
104
104
|
[tool.uv]
|
|
105
|
-
default-groups = ["dev", "test"]
|
|
105
|
+
default-groups = ["dev", "example", "test"]
|
|
106
106
|
package = true
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from collections import defaultdict
|
|
3
|
-
from collections.abc import Awaitable, Callable
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
from inspect import getmro, isclass
|
|
6
|
-
from types import GenericAlias
|
|
7
|
-
from typing import Any, Protocol, Self, TypeAliasType, runtime_checkable
|
|
8
|
-
|
|
9
|
-
import anyio
|
|
10
|
-
import injection
|
|
11
|
-
|
|
12
|
-
from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
|
|
13
|
-
|
|
14
|
-
type HandlerType[**P, T] = type[Handler[P, T]]
|
|
15
|
-
type HandlerFactory[**P, T] = Callable[..., Awaitable[Handler[P, T]]]
|
|
16
|
-
|
|
17
|
-
type Listener[T] = Callable[[T], Awaitable[Any]]
|
|
18
|
-
|
|
19
|
-
type BusType[I, O] = type[Bus[I, O]]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@runtime_checkable
|
|
23
|
-
class Handler[**P, T](Protocol):
|
|
24
|
-
__slots__ = ()
|
|
25
|
-
|
|
26
|
-
@abstractmethod
|
|
27
|
-
async def handle(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
28
|
-
raise NotImplementedError
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@runtime_checkable
|
|
32
|
-
class Bus[I, O](Dispatcher[I, O], Protocol):
|
|
33
|
-
__slots__ = ()
|
|
34
|
-
|
|
35
|
-
@abstractmethod
|
|
36
|
-
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
37
|
-
raise NotImplementedError
|
|
38
|
-
|
|
39
|
-
@abstractmethod
|
|
40
|
-
def add_listeners(self, *listeners: Listener[I]) -> Self:
|
|
41
|
-
raise NotImplementedError
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@dataclass(eq=False, frozen=True, slots=True)
|
|
45
|
-
class SubscriberDecorator[I, O]:
|
|
46
|
-
bus_type: BusType[I, O] | TypeAliasType | GenericAlias
|
|
47
|
-
injection_module: injection.Module = field(default_factory=injection.mod)
|
|
48
|
-
|
|
49
|
-
def __call__(self, first_input_type: type[I], /, *input_types: type[I]) -> Any:
|
|
50
|
-
def decorator(wrapped: type[Handler[[I], O]]) -> type[Handler[[I], O]]:
|
|
51
|
-
if not isclass(wrapped) or not issubclass(wrapped, Handler):
|
|
52
|
-
raise TypeError(f"`{wrapped}` isn't a valid handler.")
|
|
53
|
-
|
|
54
|
-
bus = self.injection_module.find_instance(self.bus_type)
|
|
55
|
-
factory = self.injection_module.make_async_factory(wrapped)
|
|
56
|
-
|
|
57
|
-
for input_type in (first_input_type, *input_types):
|
|
58
|
-
bus.subscribe(input_type, factory)
|
|
59
|
-
|
|
60
|
-
return wrapped
|
|
61
|
-
|
|
62
|
-
return decorator
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class BaseBus[I, O](BaseDispatcher[I, O], Bus[I, O], ABC):
|
|
66
|
-
__slots__ = ("__listeners",)
|
|
67
|
-
|
|
68
|
-
__listeners: list[Listener[I]]
|
|
69
|
-
|
|
70
|
-
def __init__(self) -> None:
|
|
71
|
-
super().__init__()
|
|
72
|
-
self.__listeners = []
|
|
73
|
-
|
|
74
|
-
def add_listeners(self, *listeners: Listener[I]) -> Self:
|
|
75
|
-
self.__listeners.extend(listeners)
|
|
76
|
-
return self
|
|
77
|
-
|
|
78
|
-
async def _trigger_listeners(self, input_value: I, /) -> None:
|
|
79
|
-
listeners = self.__listeners
|
|
80
|
-
|
|
81
|
-
if not listeners:
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
async with anyio.create_task_group() as task_group:
|
|
85
|
-
for listener in listeners:
|
|
86
|
-
task_group.start_soon(listener, input_value)
|
|
87
|
-
|
|
88
|
-
@staticmethod
|
|
89
|
-
def _make_handle_function(
|
|
90
|
-
handler_factory: HandlerFactory[[I], O],
|
|
91
|
-
) -> Callable[[I], Awaitable[O]]:
|
|
92
|
-
async def handle(input_value: I) -> O:
|
|
93
|
-
handler = await handler_factory()
|
|
94
|
-
return await handler.handle(input_value)
|
|
95
|
-
|
|
96
|
-
return handle
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class SimpleBus[I, O](BaseBus[I, O]):
|
|
100
|
-
__slots__ = ("__handlers",)
|
|
101
|
-
|
|
102
|
-
__handlers: dict[type[I], HandlerFactory[[I], O]]
|
|
103
|
-
|
|
104
|
-
def __init__(self) -> None:
|
|
105
|
-
super().__init__()
|
|
106
|
-
self.__handlers = {}
|
|
107
|
-
|
|
108
|
-
async def dispatch(self, input_value: I, /) -> O:
|
|
109
|
-
await self._trigger_listeners(input_value)
|
|
110
|
-
|
|
111
|
-
for input_type in getmro(type(input_value)):
|
|
112
|
-
if handler_factory := self.__handlers.get(input_type):
|
|
113
|
-
break
|
|
114
|
-
|
|
115
|
-
else:
|
|
116
|
-
return NotImplemented
|
|
117
|
-
|
|
118
|
-
handler = self._make_handle_function(handler_factory)
|
|
119
|
-
return await self._invoke_with_middlewares(handler, input_value)
|
|
120
|
-
|
|
121
|
-
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
122
|
-
if input_type in self.__handlers:
|
|
123
|
-
raise RuntimeError(
|
|
124
|
-
f"A handler is already registered for the input type: `{input_type}`."
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
self.__handlers[input_type] = factory
|
|
128
|
-
return self
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
class TaskBus[I](BaseBus[I, None]):
|
|
132
|
-
__slots__ = ("__handlers",)
|
|
133
|
-
|
|
134
|
-
__handlers: dict[type[I], list[HandlerFactory[[I], None]]]
|
|
135
|
-
|
|
136
|
-
def __init__(self) -> None:
|
|
137
|
-
super().__init__()
|
|
138
|
-
self.__handlers = defaultdict(list)
|
|
139
|
-
|
|
140
|
-
async def dispatch(self, input_value: I, /) -> None:
|
|
141
|
-
await self._trigger_listeners(input_value)
|
|
142
|
-
|
|
143
|
-
for input_type in getmro(type(input_value)):
|
|
144
|
-
if handler_factories := self.__handlers.get(input_type):
|
|
145
|
-
break
|
|
146
|
-
|
|
147
|
-
else:
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
async with anyio.create_task_group() as task_group:
|
|
151
|
-
for handler_factory in handler_factories:
|
|
152
|
-
handler = self._make_handle_function(handler_factory)
|
|
153
|
-
task_group.start_soon(
|
|
154
|
-
self._invoke_with_middlewares,
|
|
155
|
-
handler,
|
|
156
|
-
input_value,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
def subscribe(
|
|
160
|
-
self,
|
|
161
|
-
input_type: type[I],
|
|
162
|
-
factory: HandlerFactory[[I], None],
|
|
163
|
-
) -> Self:
|
|
164
|
-
self.__handlers[input_type].append(factory)
|
|
165
|
-
return self
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
import injection
|
|
5
|
-
|
|
6
|
-
from cq._core.dispatcher.bus import Bus, SimpleBus, SubscriberDecorator, TaskBus
|
|
7
|
-
from cq._core.dto import DTO
|
|
8
|
-
from cq._core.scope import CQScope
|
|
9
|
-
from cq.middlewares.scope import InjectionScopeMiddleware
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Message(DTO, ABC):
|
|
13
|
-
__slots__ = ()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Command(Message, ABC):
|
|
17
|
-
__slots__ = ()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Event(Message, ABC):
|
|
21
|
-
__slots__ = ()
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class Query(Message, ABC):
|
|
25
|
-
__slots__ = ()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type CommandBus[T] = Bus[Command, T]
|
|
29
|
-
type EventBus = Bus[Event, None]
|
|
30
|
-
type QueryBus[T] = Bus[Query, T]
|
|
31
|
-
|
|
32
|
-
AnyCommandBus = CommandBus[Any]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
command_handler: SubscriberDecorator[Command, Any] = SubscriberDecorator(CommandBus)
|
|
36
|
-
event_handler: SubscriberDecorator[Event, None] = SubscriberDecorator(EventBus)
|
|
37
|
-
query_handler: SubscriberDecorator[Query, Any] = SubscriberDecorator(QueryBus)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@injection.inject
|
|
41
|
-
def get_command_bus[T](bus: CommandBus[T] = NotImplemented, /) -> CommandBus[T]:
|
|
42
|
-
return bus
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def new_command_bus[T]() -> CommandBus[T]:
|
|
46
|
-
bus: CommandBus[T] = SimpleBus()
|
|
47
|
-
bus.add_middlewares(
|
|
48
|
-
InjectionScopeMiddleware(CQScope.ON_COMMAND),
|
|
49
|
-
)
|
|
50
|
-
return bus
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@injection.inject
|
|
54
|
-
def get_event_bus(bus: EventBus = NotImplemented, /) -> EventBus:
|
|
55
|
-
return bus
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def new_event_bus() -> EventBus:
|
|
59
|
-
return TaskBus()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@injection.inject
|
|
63
|
-
def get_query_bus[T](bus: QueryBus[T] = NotImplemented, /) -> QueryBus[T]:
|
|
64
|
-
return bus
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def new_query_bus[T]() -> QueryBus[T]:
|
|
68
|
-
return SimpleBus()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
injection.set_constant(new_command_bus(), CommandBus, alias=True)
|
|
72
|
-
injection.set_constant(new_event_bus(), EventBus, alias=True)
|
|
73
|
-
injection.set_constant(new_query_bus(), QueryBus, alias=True)
|
|
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
|