rabbitkit 0.9.0__py3-none-any.whl
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.
- rabbitkit/__init__.py +201 -0
- rabbitkit/_version.py +3 -0
- rabbitkit/aio/__init__.py +31 -0
- rabbitkit/async_/__init__.py +9 -0
- rabbitkit/async_/batch.py +213 -0
- rabbitkit/async_/broker.py +1123 -0
- rabbitkit/async_/connection.py +274 -0
- rabbitkit/async_/pool.py +363 -0
- rabbitkit/async_/transport.py +877 -0
- rabbitkit/asyncapi/__init__.py +5 -0
- rabbitkit/asyncapi/generator.py +219 -0
- rabbitkit/asyncapi/schema.py +98 -0
- rabbitkit/cli/__init__.py +77 -0
- rabbitkit/cli/_utils.py +38 -0
- rabbitkit/cli/commands/__init__.py +0 -0
- rabbitkit/cli/commands/dlq.py +190 -0
- rabbitkit/cli/commands/health.py +34 -0
- rabbitkit/cli/commands/migrate.py +570 -0
- rabbitkit/cli/commands/routes.py +88 -0
- rabbitkit/cli/commands/run.py +144 -0
- rabbitkit/cli/commands/shell.py +72 -0
- rabbitkit/cli/commands/topology.py +346 -0
- rabbitkit/concurrency.py +451 -0
- rabbitkit/core/__init__.py +5 -0
- rabbitkit/core/app.py +323 -0
- rabbitkit/core/config.py +849 -0
- rabbitkit/core/env_config.py +251 -0
- rabbitkit/core/errors.py +199 -0
- rabbitkit/core/logging.py +261 -0
- rabbitkit/core/message.py +235 -0
- rabbitkit/core/path.py +53 -0
- rabbitkit/core/pipeline.py +1289 -0
- rabbitkit/core/protocols.py +349 -0
- rabbitkit/core/registry.py +284 -0
- rabbitkit/core/route.py +329 -0
- rabbitkit/core/router.py +142 -0
- rabbitkit/core/topology.py +261 -0
- rabbitkit/core/topology_dispatch.py +74 -0
- rabbitkit/core/types.py +324 -0
- rabbitkit/dashboard/__init__.py +5 -0
- rabbitkit/dashboard/app.py +212 -0
- rabbitkit/di/__init__.py +19 -0
- rabbitkit/di/context.py +193 -0
- rabbitkit/di/depends.py +42 -0
- rabbitkit/di/resolver.py +503 -0
- rabbitkit/dlq.py +320 -0
- rabbitkit/experimental/__init__.py +50 -0
- rabbitkit/fastapi.py +91 -0
- rabbitkit/health.py +654 -0
- rabbitkit/highload/__init__.py +10 -0
- rabbitkit/highload/backpressure.py +514 -0
- rabbitkit/highload/batch.py +448 -0
- rabbitkit/locking.py +277 -0
- rabbitkit/management.py +470 -0
- rabbitkit/middleware/__init__.py +27 -0
- rabbitkit/middleware/base.py +125 -0
- rabbitkit/middleware/circuit_breaker.py +131 -0
- rabbitkit/middleware/compression.py +267 -0
- rabbitkit/middleware/deduplication.py +651 -0
- rabbitkit/middleware/error_classifier.py +43 -0
- rabbitkit/middleware/exception.py +105 -0
- rabbitkit/middleware/metrics.py +440 -0
- rabbitkit/middleware/otel.py +203 -0
- rabbitkit/middleware/rate_limit.py +247 -0
- rabbitkit/middleware/retry.py +540 -0
- rabbitkit/middleware/signing.py +682 -0
- rabbitkit/middleware/timeout.py +291 -0
- rabbitkit/py.typed +0 -0
- rabbitkit/queue_metrics.py +174 -0
- rabbitkit/results/__init__.py +6 -0
- rabbitkit/results/backend.py +102 -0
- rabbitkit/results/middleware.py +123 -0
- rabbitkit/rpc.py +632 -0
- rabbitkit/serialization/__init__.py +25 -0
- rabbitkit/serialization/base.py +35 -0
- rabbitkit/serialization/json.py +122 -0
- rabbitkit/serialization/msgspec.py +136 -0
- rabbitkit/serialization/pipeline.py +255 -0
- rabbitkit/streams.py +139 -0
- rabbitkit/sync/__init__.py +11 -0
- rabbitkit/sync/batch.py +595 -0
- rabbitkit/sync/broker.py +996 -0
- rabbitkit/sync/connection.py +209 -0
- rabbitkit/sync/pool.py +262 -0
- rabbitkit/sync/transport.py +1085 -0
- rabbitkit/testing/__init__.py +20 -0
- rabbitkit/testing/app.py +99 -0
- rabbitkit/testing/broker.py +540 -0
- rabbitkit/testing/fixtures.py +56 -0
- rabbitkit-0.9.0.dist-info/METADATA +575 -0
- rabbitkit-0.9.0.dist-info/RECORD +95 -0
- rabbitkit-0.9.0.dist-info/WHEEL +5 -0
- rabbitkit-0.9.0.dist-info/entry_points.txt +2 -0
- rabbitkit-0.9.0.dist-info/licenses/LICENSE +21 -0
- rabbitkit-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Result storage middleware — stores handler return values.
|
|
2
|
+
|
|
3
|
+
``ResultMiddleware`` intercepts handler return values and persists them to a
|
|
4
|
+
``ResultBackend`` (e.g. ``RedisResultBackend``) keyed by ``correlation_id``.
|
|
5
|
+
|
|
6
|
+
This enables the **fire-and-retrieve** pattern:
|
|
7
|
+
|
|
8
|
+
1. Publisher sends a message with a ``correlation_id``.
|
|
9
|
+
2. Consumer handler returns a result.
|
|
10
|
+
3. ``ResultMiddleware`` stores ``result`` at ``backend[correlation_id]``.
|
|
11
|
+
4. Publisher polls / waits for ``backend.fetch(correlation_id)``.
|
|
12
|
+
|
|
13
|
+
Quick start
|
|
14
|
+
-----------
|
|
15
|
+
import redis
|
|
16
|
+
from rabbitkit.results.backend import RedisResultBackend
|
|
17
|
+
from rabbitkit.results.middleware import ResultMiddleware
|
|
18
|
+
|
|
19
|
+
r = redis.Redis()
|
|
20
|
+
backend = RedisResultBackend(r, key_prefix="orders:result:")
|
|
21
|
+
result_mw = ResultMiddleware(backend, ttl=300)
|
|
22
|
+
|
|
23
|
+
@broker.subscriber(queue="orders", middlewares=[result_mw])
|
|
24
|
+
def handle_order(body: bytes) -> bytes:
|
|
25
|
+
# This return value is automatically stored in Redis
|
|
26
|
+
return b'{"processed": true}'
|
|
27
|
+
|
|
28
|
+
Retrieving from the caller side::
|
|
29
|
+
|
|
30
|
+
import time
|
|
31
|
+
result = None
|
|
32
|
+
for _ in range(50): # poll for up to 5 s
|
|
33
|
+
result = backend.fetch(correlation_id, timeout=0.1)
|
|
34
|
+
if result is not None:
|
|
35
|
+
break
|
|
36
|
+
time.sleep(0.1)
|
|
37
|
+
|
|
38
|
+
Custom serializer
|
|
39
|
+
-----------------
|
|
40
|
+
Pass a serializer to control how non-bytes return values are encoded:
|
|
41
|
+
|
|
42
|
+
from rabbitkit.serialization.json import JsonSerializer
|
|
43
|
+
|
|
44
|
+
result_mw = ResultMiddleware(backend, serializer=JsonSerializer(), ttl=600)
|
|
45
|
+
|
|
46
|
+
@broker.subscriber(queue="calc", middlewares=[result_mw])
|
|
47
|
+
def compute(body: bytes) -> dict:
|
|
48
|
+
return {"answer": 42} # encoded with JsonSerializer before storage
|
|
49
|
+
|
|
50
|
+
Messages without ``correlation_id``
|
|
51
|
+
-------------------------------------
|
|
52
|
+
If the incoming message has no ``correlation_id``, the result is silently
|
|
53
|
+
discarded (no exception raised).
|
|
54
|
+
|
|
55
|
+
Non-JSON-native results (H13)
|
|
56
|
+
------------------------------
|
|
57
|
+
Without an explicit ``serializer=``, a return value that ``json.dumps`` can't
|
|
58
|
+
encode natively (a custom class, ``object()``, etc.) raises ``TypeError``
|
|
59
|
+
instead of being silently stringified — pass a ``serializer=`` for types
|
|
60
|
+
JSON can't represent. A handler that returns an exception *instance* as data
|
|
61
|
+
(not by raising it) is stored as an explicit, marked error envelope
|
|
62
|
+
(``{"__rabbitkit_error__": true, "type": ..., "message": ...}``) instead of
|
|
63
|
+
being indistinguishable from a normal string result.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
from __future__ import annotations
|
|
67
|
+
|
|
68
|
+
import json
|
|
69
|
+
from typing import Any
|
|
70
|
+
|
|
71
|
+
from rabbitkit.core.message import RabbitMessage
|
|
72
|
+
from rabbitkit.middleware.base import BaseMiddleware
|
|
73
|
+
from rabbitkit.results.backend import ResultBackend
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ResultMiddleware(BaseMiddleware):
|
|
77
|
+
"""Stores handler return values in a result backend.
|
|
78
|
+
|
|
79
|
+
Keyed by the message's correlation_id. If no correlation_id, skips storage.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, backend: ResultBackend[Any], serializer: Any | None = None, ttl: int = 3600) -> None:
|
|
83
|
+
self._backend = backend
|
|
84
|
+
self._serializer = serializer
|
|
85
|
+
self._ttl = ttl
|
|
86
|
+
|
|
87
|
+
def _serialize(self, result: Any) -> bytes:
|
|
88
|
+
"""Encode a handler's return value for storage.
|
|
89
|
+
|
|
90
|
+
H13: no ``default=str`` fallback — a non-JSON-native object (custom
|
|
91
|
+
class, ``exception``, etc.) raises ``TypeError`` here instead of
|
|
92
|
+
being silently stringified into a lossy blob indistinguishable from
|
|
93
|
+
a real result. An exception specifically gets an explicit,
|
|
94
|
+
marked error envelope (``__rabbitkit_error__``) rather than either
|
|
95
|
+
of those, since "the handler returned an exception object as data"
|
|
96
|
+
is a legitimate pattern worth preserving in a decodable, unambiguous
|
|
97
|
+
shape. Pass an explicit ``serializer=`` to support other
|
|
98
|
+
non-JSON-native result types.
|
|
99
|
+
"""
|
|
100
|
+
if isinstance(result, bytes):
|
|
101
|
+
return result
|
|
102
|
+
if self._serializer is not None and hasattr(self._serializer, "encode"):
|
|
103
|
+
return self._serializer.encode(result) # type: ignore[no-any-return]
|
|
104
|
+
if isinstance(result, BaseException):
|
|
105
|
+
envelope = {
|
|
106
|
+
"__rabbitkit_error__": True,
|
|
107
|
+
"type": type(result).__qualname__,
|
|
108
|
+
"message": str(result),
|
|
109
|
+
}
|
|
110
|
+
return json.dumps(envelope).encode("utf-8")
|
|
111
|
+
return json.dumps(result).encode("utf-8")
|
|
112
|
+
|
|
113
|
+
def consume_scope(self, call_next: Any, message: RabbitMessage) -> Any:
|
|
114
|
+
result = call_next(message)
|
|
115
|
+
if result is not None and message.correlation_id:
|
|
116
|
+
self._backend.store(message.correlation_id, self._serialize(result), self._ttl)
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
async def consume_scope_async(self, call_next: Any, message: RabbitMessage) -> Any:
|
|
120
|
+
result = await call_next(message)
|
|
121
|
+
if result is not None and message.correlation_id:
|
|
122
|
+
await self._backend.store_async(message.correlation_id, self._serialize(result), self._ttl)
|
|
123
|
+
return result
|