python-cq 0.14.1__tar.gz → 0.15.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.14.1 → python_cq-0.15.0}/PKG-INFO +1 -1
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/dispatcher/base.py +9 -1
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/dispatcher/bus.py +8 -5
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/handler.py +60 -39
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/middlewares/retry.py +1 -1
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/middlewares/scope.py +1 -1
- {python_cq-0.14.1 → python_cq-0.15.0}/pyproject.toml +1 -1
- {python_cq-0.14.1 → python_cq-0.15.0}/.gitignore +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/LICENSE +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/__init__.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/__init__.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/common/__init__.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/common/typing.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/dispatcher/__init__.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/dispatcher/lazy.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/dispatcher/pipe.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/message.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/middleware.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/pipetools.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/related_events.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/_core/scope.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/exceptions.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/ext/__init__.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/ext/fastapi.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/middlewares/__init__.py +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/cq/py.typed +0 -0
- {python_cq-0.14.1 → python_cq-0.15.0}/docs/index.md +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from collections.abc import Awaitable, Callable
|
|
3
|
+
from contextlib import AsyncExitStack, suppress
|
|
3
4
|
from typing import Protocol, Self, runtime_checkable
|
|
4
5
|
|
|
5
6
|
from cq._core.middleware import Middleware, MiddlewareGroup
|
|
@@ -40,5 +41,12 @@ class BaseDispatcher[I, O](Dispatcher[I, O], ABC):
|
|
|
40
41
|
handler: Callable[[I], Awaitable[O]],
|
|
41
42
|
input_value: I,
|
|
42
43
|
/,
|
|
44
|
+
fail_silently: bool = False,
|
|
43
45
|
) -> O:
|
|
44
|
-
|
|
46
|
+
async with AsyncExitStack() as stack:
|
|
47
|
+
if fail_silently:
|
|
48
|
+
stack.enter_context(suppress(Exception))
|
|
49
|
+
|
|
50
|
+
return await self.__middleware_group.invoke(handler, input_value)
|
|
51
|
+
|
|
52
|
+
return NotImplemented
|
|
@@ -7,6 +7,7 @@ from anyio.abc import TaskGroup
|
|
|
7
7
|
|
|
8
8
|
from cq._core.dispatcher.base import BaseDispatcher, Dispatcher
|
|
9
9
|
from cq._core.handler import (
|
|
10
|
+
HandleFunction,
|
|
10
11
|
HandlerFactory,
|
|
11
12
|
HandlerRegistry,
|
|
12
13
|
MultipleHandlerRegistry,
|
|
@@ -53,10 +54,7 @@ class BaseBus[I, O](BaseDispatcher[I, O], Bus[I, O], ABC):
|
|
|
53
54
|
self.__registry.subscribe(input_type, factory)
|
|
54
55
|
return self
|
|
55
56
|
|
|
56
|
-
def _handlers_from(
|
|
57
|
-
self,
|
|
58
|
-
input_type: type[I],
|
|
59
|
-
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
57
|
+
def _handlers_from(self, input_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
|
|
60
58
|
return self.__registry.handlers_from(input_type)
|
|
61
59
|
|
|
62
60
|
def _trigger_listeners(self, input_value: I, /, task_group: TaskGroup) -> None:
|
|
@@ -75,7 +73,11 @@ class SimpleBus[I, O](BaseBus[I, O]):
|
|
|
75
73
|
self._trigger_listeners(input_value, task_group)
|
|
76
74
|
|
|
77
75
|
for handler in self._handlers_from(type(input_value)):
|
|
78
|
-
return await self._invoke_with_middlewares(
|
|
76
|
+
return await self._invoke_with_middlewares(
|
|
77
|
+
handler,
|
|
78
|
+
input_value,
|
|
79
|
+
handler.fail_silently,
|
|
80
|
+
)
|
|
79
81
|
|
|
80
82
|
return NotImplemented
|
|
81
83
|
|
|
@@ -95,4 +97,5 @@ class TaskBus[I](BaseBus[I, None]):
|
|
|
95
97
|
self._invoke_with_middlewares,
|
|
96
98
|
handler,
|
|
97
99
|
input_value,
|
|
100
|
+
handler.fail_silently,
|
|
98
101
|
)
|
|
@@ -21,74 +21,96 @@ class Handler[**P, T](Protocol):
|
|
|
21
21
|
__slots__ = ()
|
|
22
22
|
|
|
23
23
|
@abstractmethod
|
|
24
|
-
async def handle(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
24
|
+
async def handle(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
25
25
|
raise NotImplementedError
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
29
|
+
class HandleFunction[**P, T]:
|
|
30
|
+
handler_factory: HandlerFactory[P, T]
|
|
31
|
+
handler_type: HandlerType[P, T] | None = field(default=None)
|
|
32
|
+
fail_silently: bool = field(default=False)
|
|
33
|
+
|
|
34
|
+
async def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
35
|
+
handler = await self.handler_factory()
|
|
36
|
+
return await handler.handle(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
|
|
28
39
|
@runtime_checkable
|
|
29
40
|
class HandlerRegistry[I, O](Protocol):
|
|
30
41
|
__slots__ = ()
|
|
31
42
|
|
|
32
43
|
@abstractmethod
|
|
33
|
-
def handlers_from(
|
|
34
|
-
self,
|
|
35
|
-
input_type: type[I],
|
|
36
|
-
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
44
|
+
def handlers_from(self, input_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
|
|
37
45
|
raise NotImplementedError
|
|
38
46
|
|
|
39
47
|
@abstractmethod
|
|
40
|
-
def subscribe(
|
|
48
|
+
def subscribe(
|
|
49
|
+
self,
|
|
50
|
+
input_type: type[I],
|
|
51
|
+
handler_factory: HandlerFactory[[I], O],
|
|
52
|
+
handler_type: HandlerType[[I], O] | None = ...,
|
|
53
|
+
fail_silently: bool = ...,
|
|
54
|
+
) -> Self:
|
|
41
55
|
raise NotImplementedError
|
|
42
56
|
|
|
43
57
|
|
|
44
58
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
45
59
|
class MultipleHandlerRegistry[I, O](HandlerRegistry[I, O]):
|
|
46
|
-
|
|
60
|
+
__values: dict[type[I], list[HandleFunction[[I], O]]] = field(
|
|
47
61
|
default_factory=partial(defaultdict, list),
|
|
48
62
|
init=False,
|
|
49
63
|
)
|
|
50
64
|
|
|
51
|
-
def handlers_from(
|
|
65
|
+
def handlers_from(self, input_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
|
|
66
|
+
for key_type in _iter_key_types(input_type):
|
|
67
|
+
yield from self.__values.get(key_type, ())
|
|
68
|
+
|
|
69
|
+
def subscribe(
|
|
52
70
|
self,
|
|
53
71
|
input_type: type[I],
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
handler_factory: HandlerFactory[[I], O],
|
|
73
|
+
handler_type: HandlerType[[I], O] | None = None,
|
|
74
|
+
fail_silently: bool = False,
|
|
75
|
+
) -> Self:
|
|
76
|
+
function = HandleFunction(handler_factory, handler_type, fail_silently)
|
|
58
77
|
|
|
59
|
-
def subscribe(self, input_type: type[I], factory: HandlerFactory[[I], O]) -> Self:
|
|
60
78
|
for key_type in _build_key_types(input_type):
|
|
61
|
-
self.
|
|
79
|
+
self.__values[key_type].append(function)
|
|
62
80
|
|
|
63
81
|
return self
|
|
64
82
|
|
|
65
83
|
|
|
66
84
|
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
67
85
|
class SingleHandlerRegistry[I, O](HandlerRegistry[I, O]):
|
|
68
|
-
|
|
86
|
+
__values: dict[type[I], HandleFunction[[I], O]] = field(
|
|
69
87
|
default_factory=dict,
|
|
70
88
|
init=False,
|
|
71
89
|
)
|
|
72
90
|
|
|
73
|
-
def handlers_from(
|
|
74
|
-
self,
|
|
75
|
-
input_type: type[I],
|
|
76
|
-
) -> Iterator[Callable[[I], Awaitable[O]]]:
|
|
91
|
+
def handlers_from(self, input_type: type[I]) -> Iterator[HandleFunction[[I], O]]:
|
|
77
92
|
for key_type in _iter_key_types(input_type):
|
|
78
|
-
|
|
79
|
-
if
|
|
80
|
-
yield
|
|
93
|
+
function = self.__values.get(key_type, None)
|
|
94
|
+
if function is not None:
|
|
95
|
+
yield function
|
|
81
96
|
|
|
82
|
-
def subscribe(
|
|
83
|
-
|
|
97
|
+
def subscribe(
|
|
98
|
+
self,
|
|
99
|
+
input_type: type[I],
|
|
100
|
+
handler_factory: HandlerFactory[[I], O],
|
|
101
|
+
handler_type: HandlerType[[I], O] | None = None,
|
|
102
|
+
fail_silently: bool = False,
|
|
103
|
+
) -> Self:
|
|
104
|
+
function = HandleFunction(handler_factory, handler_type, fail_silently)
|
|
105
|
+
entries = {key_type: function for key_type in _build_key_types(input_type)}
|
|
84
106
|
|
|
85
107
|
for key_type in entries:
|
|
86
|
-
if key_type in self.
|
|
108
|
+
if key_type in self.__values:
|
|
87
109
|
raise RuntimeError(
|
|
88
110
|
f"A handler is already registered for the input type: `{key_type}`."
|
|
89
111
|
)
|
|
90
112
|
|
|
91
|
-
self.
|
|
113
|
+
self.__values.update(entries)
|
|
92
114
|
return self
|
|
93
115
|
|
|
94
116
|
|
|
@@ -105,6 +127,7 @@ class HandlerDecorator[I, O]:
|
|
|
105
127
|
input_or_handler_type: type[I],
|
|
106
128
|
/,
|
|
107
129
|
*,
|
|
130
|
+
fail_silently: bool = ...,
|
|
108
131
|
threadsafe: bool | None = ...,
|
|
109
132
|
) -> Decorator: ...
|
|
110
133
|
|
|
@@ -114,6 +137,7 @@ class HandlerDecorator[I, O]:
|
|
|
114
137
|
input_or_handler_type: T,
|
|
115
138
|
/,
|
|
116
139
|
*,
|
|
140
|
+
fail_silently: bool = ...,
|
|
117
141
|
threadsafe: bool | None = ...,
|
|
118
142
|
) -> T: ...
|
|
119
143
|
|
|
@@ -123,6 +147,7 @@ class HandlerDecorator[I, O]:
|
|
|
123
147
|
input_or_handler_type: None = ...,
|
|
124
148
|
/,
|
|
125
149
|
*,
|
|
150
|
+
fail_silently: bool = ...,
|
|
126
151
|
threadsafe: bool | None = ...,
|
|
127
152
|
) -> Decorator: ...
|
|
128
153
|
|
|
@@ -131,6 +156,7 @@ class HandlerDecorator[I, O]:
|
|
|
131
156
|
input_or_handler_type: type[I] | T | None = None,
|
|
132
157
|
/,
|
|
133
158
|
*,
|
|
159
|
+
fail_silently: bool = False,
|
|
134
160
|
threadsafe: bool | None = None,
|
|
135
161
|
) -> Any:
|
|
136
162
|
if (
|
|
@@ -138,11 +164,16 @@ class HandlerDecorator[I, O]:
|
|
|
138
164
|
and isclass(input_or_handler_type)
|
|
139
165
|
and issubclass(input_or_handler_type, Handler)
|
|
140
166
|
):
|
|
141
|
-
return self.__decorator(
|
|
167
|
+
return self.__decorator(
|
|
168
|
+
input_or_handler_type,
|
|
169
|
+
fail_silently=fail_silently,
|
|
170
|
+
threadsafe=threadsafe,
|
|
171
|
+
)
|
|
142
172
|
|
|
143
173
|
return partial(
|
|
144
174
|
self.__decorator,
|
|
145
175
|
input_type=input_or_handler_type, # type: ignore[arg-type]
|
|
176
|
+
fail_silently=fail_silently,
|
|
146
177
|
threadsafe=threadsafe,
|
|
147
178
|
)
|
|
148
179
|
|
|
@@ -152,11 +183,12 @@ class HandlerDecorator[I, O]:
|
|
|
152
183
|
/,
|
|
153
184
|
*,
|
|
154
185
|
input_type: type[I] | None = None,
|
|
186
|
+
fail_silently: bool = False,
|
|
155
187
|
threadsafe: bool | None = None,
|
|
156
188
|
) -> HandlerType[[I], O]:
|
|
157
189
|
factory = self.injection_module.make_async_factory(wrapped, threadsafe)
|
|
158
190
|
input_type = input_type or _resolve_input_type(wrapped)
|
|
159
|
-
self.registry.subscribe(input_type, factory)
|
|
191
|
+
self.registry.subscribe(input_type, factory, wrapped, fail_silently)
|
|
160
192
|
return wrapped
|
|
161
193
|
|
|
162
194
|
|
|
@@ -190,14 +222,3 @@ def _resolve_input_type[I, O](handler_type: HandlerType[[I], O]) -> type[I]:
|
|
|
190
222
|
f"Unable to resolve input type for handler `{handler_type}`, "
|
|
191
223
|
"`handle` method must have a type annotation for its first parameter."
|
|
192
224
|
)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def _make_handle_function[I, O](
|
|
196
|
-
factory: HandlerFactory[[I], O],
|
|
197
|
-
) -> Callable[[I], Awaitable[O]]:
|
|
198
|
-
return partial(__handle, factory=factory)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
async def __handle[I, O](input_value: I, *, factory: HandlerFactory[[I], O]) -> O:
|
|
202
|
-
handler = await factory()
|
|
203
|
-
return await handler.handle(input_value)
|
|
@@ -25,7 +25,7 @@ class RetryMiddleware:
|
|
|
25
25
|
self.__exceptions = tuple(exceptions)
|
|
26
26
|
self.__retry = retry
|
|
27
27
|
|
|
28
|
-
async def __call__(self, *args: Any, **kwargs: Any) -> MiddlewareResult[Any]:
|
|
28
|
+
async def __call__(self, /, *args: Any, **kwargs: Any) -> MiddlewareResult[Any]:
|
|
29
29
|
retry = self.__retry
|
|
30
30
|
|
|
31
31
|
for attempt in range(1, retry + 1):
|
|
@@ -19,7 +19,7 @@ class InjectionScopeMiddleware:
|
|
|
19
19
|
exist_ok: bool = field(default=False, kw_only=True)
|
|
20
20
|
threadsafe: bool | None = field(default=None, kw_only=True)
|
|
21
21
|
|
|
22
|
-
async def __call__(self, *args: Any, **kwargs: Any) -> MiddlewareResult[Any]:
|
|
22
|
+
async def __call__(self, /, *args: Any, **kwargs: Any) -> MiddlewareResult[Any]:
|
|
23
23
|
async with AsyncExitStack() as stack:
|
|
24
24
|
try:
|
|
25
25
|
await stack.enter_async_context(
|
|
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
|