python-cq 0.18.0__tar.gz → 0.20.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.18.0 → python_cq-0.20.0}/PKG-INFO +1 -1
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/__init__.py +2 -1
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/dispatchers/abc.py +1 -1
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/dispatchers/bus.py +2 -6
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/dispatchers/pipe.py +1 -1
- python_cq-0.20.0/cq/_core/pump.py +56 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/queues/abc.py +7 -6
- python_cq-0.20.0/cq/_core/queues/memory.py +60 -0
- python_cq-0.20.0/cq/middlewares/exc.py +56 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/pyproject.toml +1 -1
- python_cq-0.18.0/cq/_core/pump.py +0 -34
- python_cq-0.18.0/cq/_core/queues/memory.py +0 -42
- {python_cq-0.18.0 → python_cq-0.20.0}/.gitignore +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/LICENSE +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/common/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/common/typing.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/cq.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/di.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/dispatchers/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/dispatchers/lazy.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/handler.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/message.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/middleware.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/middlewares/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/middlewares/scope.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/pipetools.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/queues/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/_core/related_events.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/exceptions.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/ext/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/ext/injection.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/middlewares/__init__.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/middlewares/retry.py +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/cq/py.typed +0 -0
- {python_cq-0.18.0 → python_cq-0.20.0}/docs/index.md +0 -0
|
@@ -16,7 +16,7 @@ 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
|
|
|
@@ -30,6 +30,7 @@ __all__ = (
|
|
|
30
30
|
"Consumer",
|
|
31
31
|
"ContextCommandPipeline",
|
|
32
32
|
"ContextPipeline",
|
|
33
|
+
"Delivery",
|
|
33
34
|
"DIAdapter",
|
|
34
35
|
"Dispatcher",
|
|
35
36
|
"Event",
|
|
@@ -83,11 +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.
|
|
87
|
-
handler,
|
|
88
|
-
message,
|
|
89
|
-
handler.fail_silently,
|
|
90
|
-
)
|
|
86
|
+
return await self._invoke(handler, message, handler.fail_silently)
|
|
91
87
|
|
|
92
88
|
return NotImplemented
|
|
93
89
|
|
|
@@ -104,7 +100,7 @@ class TaskBus[I](BaseBus[I, None]):
|
|
|
104
100
|
|
|
105
101
|
for handler in self._handlers_from(type(message)):
|
|
106
102
|
task_group.start_soon(
|
|
107
|
-
self.
|
|
103
|
+
self._invoke,
|
|
108
104
|
handler,
|
|
109
105
|
message,
|
|
110
106
|
handler.fail_silently,
|
|
@@ -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.
|
|
143
|
+
return await self._invoke(self.__steps.execute, message)
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
class ContextPipeline[I]:
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
import anyio
|
|
7
|
+
|
|
8
|
+
from cq._core.middleware import Middleware, MiddlewareGroup
|
|
9
|
+
from cq._core.queues.abc import Consumer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
13
|
+
class Pump[T]:
|
|
14
|
+
consumer: Consumer[T]
|
|
15
|
+
dispatcher: Callable[[T], Awaitable[Any]]
|
|
16
|
+
fail_silently: bool = field(default=False)
|
|
17
|
+
__middleware_group: MiddlewareGroup[[T], Any] = field(
|
|
18
|
+
default_factory=MiddlewareGroup,
|
|
19
|
+
init=False,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def add_middlewares(self, *middlewares: Middleware[[T], Any]) -> Self:
|
|
23
|
+
self.__middleware_group.add(*middlewares)
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
async def drain(self) -> None:
|
|
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
|
+
|
|
35
|
+
@asynccontextmanager
|
|
36
|
+
async def draining(
|
|
37
|
+
self,
|
|
38
|
+
/,
|
|
39
|
+
*,
|
|
40
|
+
concurrency: int | None = None,
|
|
41
|
+
graceful: bool = False,
|
|
42
|
+
) -> AsyncIterator[None]:
|
|
43
|
+
if concurrency is None:
|
|
44
|
+
concurrency = 1
|
|
45
|
+
elif concurrency < 1:
|
|
46
|
+
raise ValueError(f"`concurrency` must be at least 1, got {concurrency}.")
|
|
47
|
+
|
|
48
|
+
async with anyio.create_task_group() as task_group:
|
|
49
|
+
for _ in range(concurrency):
|
|
50
|
+
task_group.start_soon(self.drain)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
yield
|
|
54
|
+
finally:
|
|
55
|
+
if not graceful:
|
|
56
|
+
task_group.cancel_scope.cancel()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from abc import abstractmethod
|
|
2
|
-
from collections.abc import
|
|
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
|
|
19
|
+
class Delivery[T](AsyncContextManager[T], Protocol):
|
|
20
20
|
__slots__ = ()
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class Consumer[T](AsyncIterable[Delivery[T]], Protocol):
|
|
25
|
+
__slots__ = ()
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
@runtime_checkable
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
|
|
2
|
+
from contextlib import asynccontextmanager, nullcontext
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
import anyio
|
|
7
|
+
from anyio.abc import ObjectReceiveStream, ObjectSendStream
|
|
8
|
+
|
|
9
|
+
from cq._core.middleware import Middleware
|
|
10
|
+
from cq._core.pump import Pump
|
|
11
|
+
from cq._core.queues.abc import Delivery, Queue
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MemoryQueue[T](Queue[T]):
|
|
15
|
+
__slots__ = ("__consumer", "__producer")
|
|
16
|
+
|
|
17
|
+
__consumer: ObjectReceiveStream[T]
|
|
18
|
+
__producer: ObjectSendStream[T]
|
|
19
|
+
|
|
20
|
+
def __init__(self, maxsize: int = 0) -> None:
|
|
21
|
+
self.__producer, self.__consumer = anyio.create_memory_object_stream(maxsize)
|
|
22
|
+
|
|
23
|
+
async def __aenter__(self) -> Self:
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
async def __aexit__(
|
|
27
|
+
self,
|
|
28
|
+
exc_type: type[BaseException] | None,
|
|
29
|
+
exc_value: BaseException | None,
|
|
30
|
+
traceback: TracebackType | None,
|
|
31
|
+
) -> None:
|
|
32
|
+
await self.close()
|
|
33
|
+
|
|
34
|
+
async def __aiter__(self) -> AsyncIterator[Delivery[T]]:
|
|
35
|
+
async for message in self.__consumer:
|
|
36
|
+
yield nullcontext(message)
|
|
37
|
+
|
|
38
|
+
async def close(self) -> None:
|
|
39
|
+
await self.__producer.aclose()
|
|
40
|
+
|
|
41
|
+
@asynccontextmanager
|
|
42
|
+
async def draining(
|
|
43
|
+
self,
|
|
44
|
+
dispatcher: Callable[[T], Awaitable[Any]],
|
|
45
|
+
/,
|
|
46
|
+
*,
|
|
47
|
+
concurrency: int | None = None,
|
|
48
|
+
fail_silently: bool = False,
|
|
49
|
+
middlewares: Sequence[Middleware[[T], Any]] = (),
|
|
50
|
+
) -> AsyncIterator[Self]:
|
|
51
|
+
async with (
|
|
52
|
+
Pump(self, dispatcher, fail_silently)
|
|
53
|
+
.add_middlewares(*middlewares)
|
|
54
|
+
.draining(concurrency=concurrency, graceful=True)
|
|
55
|
+
):
|
|
56
|
+
async with self:
|
|
57
|
+
yield self
|
|
58
|
+
|
|
59
|
+
async def send(self, message: T, /) -> None:
|
|
60
|
+
await self.__producer.send(message)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
2
|
+
from typing import Any, Concatenate, Self
|
|
3
|
+
|
|
4
|
+
from cq import MiddlewareResult
|
|
5
|
+
|
|
6
|
+
__all__ = ("CaptureExceptionMiddleware",)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CaptureExceptionMiddleware[**P, Exc: BaseException]:
|
|
10
|
+
__slots__ = ("__exceptions", "__on_error", "__reraise")
|
|
11
|
+
|
|
12
|
+
__exceptions: tuple[type[Exc], ...]
|
|
13
|
+
__on_error: Callable[Concatenate[Exc, P], Awaitable[Any]]
|
|
14
|
+
__reraise: bool
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
on_error: Callable[Concatenate[Exc, P], Awaitable[Any]],
|
|
19
|
+
/,
|
|
20
|
+
exceptions: Sequence[type[Exc]] | None = None,
|
|
21
|
+
reraise: bool = False,
|
|
22
|
+
) -> None:
|
|
23
|
+
self.__exceptions = (Exception,) if exceptions is None else tuple(exceptions) # type: ignore[assignment]
|
|
24
|
+
self.__on_error = on_error
|
|
25
|
+
self.__reraise = reraise
|
|
26
|
+
|
|
27
|
+
async def __call__(
|
|
28
|
+
self,
|
|
29
|
+
/,
|
|
30
|
+
*args: P.args,
|
|
31
|
+
**kwargs: P.kwargs,
|
|
32
|
+
) -> MiddlewareResult[Any]:
|
|
33
|
+
try:
|
|
34
|
+
yield
|
|
35
|
+
except self.__exceptions as exc:
|
|
36
|
+
await self.__on_error(exc, *args, **kwargs)
|
|
37
|
+
if self.__reraise:
|
|
38
|
+
raise
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def sync(
|
|
42
|
+
cls,
|
|
43
|
+
on_error: Callable[Concatenate[Exc, P], Any],
|
|
44
|
+
/,
|
|
45
|
+
exceptions: Sequence[type[Exc]] | None = None,
|
|
46
|
+
reraise: bool = False,
|
|
47
|
+
) -> Self:
|
|
48
|
+
async def async_on_error(
|
|
49
|
+
exception: Exc,
|
|
50
|
+
/,
|
|
51
|
+
*args: P.args,
|
|
52
|
+
**kwargs: P.kwargs,
|
|
53
|
+
) -> Any:
|
|
54
|
+
return on_error(exception, *args, **kwargs)
|
|
55
|
+
|
|
56
|
+
return cls(async_on_error, exceptions, reraise)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
2
|
-
from contextlib import asynccontextmanager
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
import anyio
|
|
7
|
-
|
|
8
|
-
from cq._core.queues.abc import Consumer
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass(repr=False, eq=False, frozen=True, slots=True)
|
|
12
|
-
class Pump[T]:
|
|
13
|
-
consumer: Consumer[T]
|
|
14
|
-
dispatcher: Callable[[T], Awaitable[Any]]
|
|
15
|
-
fail_silently: bool = field(default=False)
|
|
16
|
-
|
|
17
|
-
async def drain(self) -> None:
|
|
18
|
-
async for message in self.consumer:
|
|
19
|
-
try:
|
|
20
|
-
await self.dispatcher(message)
|
|
21
|
-
except Exception:
|
|
22
|
-
if not self.fail_silently:
|
|
23
|
-
raise
|
|
24
|
-
|
|
25
|
-
@asynccontextmanager
|
|
26
|
-
async def draining(self, /, *, graceful: bool = False) -> AsyncIterator[None]:
|
|
27
|
-
async with anyio.create_task_group() as task_group:
|
|
28
|
-
task_group.start_soon(self.drain)
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
yield
|
|
32
|
-
finally:
|
|
33
|
-
if not graceful:
|
|
34
|
-
task_group.cancel_scope.cancel()
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
2
|
-
from contextlib import asynccontextmanager
|
|
3
|
-
from typing import Any, Self
|
|
4
|
-
|
|
5
|
-
import anyio
|
|
6
|
-
from anyio.abc import ObjectReceiveStream, ObjectSendStream
|
|
7
|
-
|
|
8
|
-
from cq._core.pump import Pump
|
|
9
|
-
from cq._core.queues.abc import Queue
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class MemoryQueue[T](Queue[T]):
|
|
13
|
-
__slots__ = ("__consumer", "__producer")
|
|
14
|
-
|
|
15
|
-
__consumer: ObjectReceiveStream[T]
|
|
16
|
-
__producer: ObjectSendStream[T]
|
|
17
|
-
|
|
18
|
-
def __init__(self, maxsize: int = 0) -> None:
|
|
19
|
-
self.__producer, self.__consumer = anyio.create_memory_object_stream(maxsize)
|
|
20
|
-
|
|
21
|
-
def __aiter__(self) -> AsyncIterator[T]:
|
|
22
|
-
return aiter(self.__consumer)
|
|
23
|
-
|
|
24
|
-
async def close(self) -> None:
|
|
25
|
-
await self.__producer.aclose()
|
|
26
|
-
|
|
27
|
-
@asynccontextmanager
|
|
28
|
-
async def draining(
|
|
29
|
-
self,
|
|
30
|
-
dispatcher: Callable[[T], Awaitable[Any]],
|
|
31
|
-
/,
|
|
32
|
-
*,
|
|
33
|
-
fail_silently: bool = False,
|
|
34
|
-
) -> AsyncIterator[Self]:
|
|
35
|
-
async with Pump(self, dispatcher, fail_silently).draining(graceful=True):
|
|
36
|
-
try:
|
|
37
|
-
yield self
|
|
38
|
-
finally:
|
|
39
|
-
await self.close()
|
|
40
|
-
|
|
41
|
-
async def send(self, message: T, /) -> None:
|
|
42
|
-
await self.__producer.send(message)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|