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