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.
Files changed (95) hide show
  1. rabbitkit/__init__.py +201 -0
  2. rabbitkit/_version.py +3 -0
  3. rabbitkit/aio/__init__.py +31 -0
  4. rabbitkit/async_/__init__.py +9 -0
  5. rabbitkit/async_/batch.py +213 -0
  6. rabbitkit/async_/broker.py +1123 -0
  7. rabbitkit/async_/connection.py +274 -0
  8. rabbitkit/async_/pool.py +363 -0
  9. rabbitkit/async_/transport.py +877 -0
  10. rabbitkit/asyncapi/__init__.py +5 -0
  11. rabbitkit/asyncapi/generator.py +219 -0
  12. rabbitkit/asyncapi/schema.py +98 -0
  13. rabbitkit/cli/__init__.py +77 -0
  14. rabbitkit/cli/_utils.py +38 -0
  15. rabbitkit/cli/commands/__init__.py +0 -0
  16. rabbitkit/cli/commands/dlq.py +190 -0
  17. rabbitkit/cli/commands/health.py +34 -0
  18. rabbitkit/cli/commands/migrate.py +570 -0
  19. rabbitkit/cli/commands/routes.py +88 -0
  20. rabbitkit/cli/commands/run.py +144 -0
  21. rabbitkit/cli/commands/shell.py +72 -0
  22. rabbitkit/cli/commands/topology.py +346 -0
  23. rabbitkit/concurrency.py +451 -0
  24. rabbitkit/core/__init__.py +5 -0
  25. rabbitkit/core/app.py +323 -0
  26. rabbitkit/core/config.py +849 -0
  27. rabbitkit/core/env_config.py +251 -0
  28. rabbitkit/core/errors.py +199 -0
  29. rabbitkit/core/logging.py +261 -0
  30. rabbitkit/core/message.py +235 -0
  31. rabbitkit/core/path.py +53 -0
  32. rabbitkit/core/pipeline.py +1289 -0
  33. rabbitkit/core/protocols.py +349 -0
  34. rabbitkit/core/registry.py +284 -0
  35. rabbitkit/core/route.py +329 -0
  36. rabbitkit/core/router.py +142 -0
  37. rabbitkit/core/topology.py +261 -0
  38. rabbitkit/core/topology_dispatch.py +74 -0
  39. rabbitkit/core/types.py +324 -0
  40. rabbitkit/dashboard/__init__.py +5 -0
  41. rabbitkit/dashboard/app.py +212 -0
  42. rabbitkit/di/__init__.py +19 -0
  43. rabbitkit/di/context.py +193 -0
  44. rabbitkit/di/depends.py +42 -0
  45. rabbitkit/di/resolver.py +503 -0
  46. rabbitkit/dlq.py +320 -0
  47. rabbitkit/experimental/__init__.py +50 -0
  48. rabbitkit/fastapi.py +91 -0
  49. rabbitkit/health.py +654 -0
  50. rabbitkit/highload/__init__.py +10 -0
  51. rabbitkit/highload/backpressure.py +514 -0
  52. rabbitkit/highload/batch.py +448 -0
  53. rabbitkit/locking.py +277 -0
  54. rabbitkit/management.py +470 -0
  55. rabbitkit/middleware/__init__.py +27 -0
  56. rabbitkit/middleware/base.py +125 -0
  57. rabbitkit/middleware/circuit_breaker.py +131 -0
  58. rabbitkit/middleware/compression.py +267 -0
  59. rabbitkit/middleware/deduplication.py +651 -0
  60. rabbitkit/middleware/error_classifier.py +43 -0
  61. rabbitkit/middleware/exception.py +105 -0
  62. rabbitkit/middleware/metrics.py +440 -0
  63. rabbitkit/middleware/otel.py +203 -0
  64. rabbitkit/middleware/rate_limit.py +247 -0
  65. rabbitkit/middleware/retry.py +540 -0
  66. rabbitkit/middleware/signing.py +682 -0
  67. rabbitkit/middleware/timeout.py +291 -0
  68. rabbitkit/py.typed +0 -0
  69. rabbitkit/queue_metrics.py +174 -0
  70. rabbitkit/results/__init__.py +6 -0
  71. rabbitkit/results/backend.py +102 -0
  72. rabbitkit/results/middleware.py +123 -0
  73. rabbitkit/rpc.py +632 -0
  74. rabbitkit/serialization/__init__.py +25 -0
  75. rabbitkit/serialization/base.py +35 -0
  76. rabbitkit/serialization/json.py +122 -0
  77. rabbitkit/serialization/msgspec.py +136 -0
  78. rabbitkit/serialization/pipeline.py +255 -0
  79. rabbitkit/streams.py +139 -0
  80. rabbitkit/sync/__init__.py +11 -0
  81. rabbitkit/sync/batch.py +595 -0
  82. rabbitkit/sync/broker.py +996 -0
  83. rabbitkit/sync/connection.py +209 -0
  84. rabbitkit/sync/pool.py +262 -0
  85. rabbitkit/sync/transport.py +1085 -0
  86. rabbitkit/testing/__init__.py +20 -0
  87. rabbitkit/testing/app.py +99 -0
  88. rabbitkit/testing/broker.py +540 -0
  89. rabbitkit/testing/fixtures.py +56 -0
  90. rabbitkit-0.9.0.dist-info/METADATA +575 -0
  91. rabbitkit-0.9.0.dist-info/RECORD +95 -0
  92. rabbitkit-0.9.0.dist-info/WHEEL +5 -0
  93. rabbitkit-0.9.0.dist-info/entry_points.txt +2 -0
  94. rabbitkit-0.9.0.dist-info/licenses/LICENSE +21 -0
  95. 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