rabbitkit 0.9.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.
- rabbitkit-0.9.0/LICENSE +21 -0
- rabbitkit-0.9.0/PKG-INFO +575 -0
- rabbitkit-0.9.0/README.md +497 -0
- rabbitkit-0.9.0/pyproject.toml +140 -0
- rabbitkit-0.9.0/setup.cfg +4 -0
- rabbitkit-0.9.0/src/rabbitkit/__init__.py +201 -0
- rabbitkit-0.9.0/src/rabbitkit/_version.py +3 -0
- rabbitkit-0.9.0/src/rabbitkit/aio/__init__.py +31 -0
- rabbitkit-0.9.0/src/rabbitkit/async_/__init__.py +9 -0
- rabbitkit-0.9.0/src/rabbitkit/async_/batch.py +213 -0
- rabbitkit-0.9.0/src/rabbitkit/async_/broker.py +1123 -0
- rabbitkit-0.9.0/src/rabbitkit/async_/connection.py +274 -0
- rabbitkit-0.9.0/src/rabbitkit/async_/pool.py +363 -0
- rabbitkit-0.9.0/src/rabbitkit/async_/transport.py +877 -0
- rabbitkit-0.9.0/src/rabbitkit/asyncapi/__init__.py +5 -0
- rabbitkit-0.9.0/src/rabbitkit/asyncapi/generator.py +219 -0
- rabbitkit-0.9.0/src/rabbitkit/asyncapi/schema.py +98 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/__init__.py +77 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/_utils.py +38 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/__init__.py +0 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/dlq.py +190 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/health.py +34 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/migrate.py +570 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/routes.py +88 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/run.py +144 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/shell.py +72 -0
- rabbitkit-0.9.0/src/rabbitkit/cli/commands/topology.py +346 -0
- rabbitkit-0.9.0/src/rabbitkit/concurrency.py +451 -0
- rabbitkit-0.9.0/src/rabbitkit/core/__init__.py +5 -0
- rabbitkit-0.9.0/src/rabbitkit/core/app.py +323 -0
- rabbitkit-0.9.0/src/rabbitkit/core/config.py +849 -0
- rabbitkit-0.9.0/src/rabbitkit/core/env_config.py +251 -0
- rabbitkit-0.9.0/src/rabbitkit/core/errors.py +199 -0
- rabbitkit-0.9.0/src/rabbitkit/core/logging.py +261 -0
- rabbitkit-0.9.0/src/rabbitkit/core/message.py +235 -0
- rabbitkit-0.9.0/src/rabbitkit/core/path.py +53 -0
- rabbitkit-0.9.0/src/rabbitkit/core/pipeline.py +1289 -0
- rabbitkit-0.9.0/src/rabbitkit/core/protocols.py +349 -0
- rabbitkit-0.9.0/src/rabbitkit/core/registry.py +284 -0
- rabbitkit-0.9.0/src/rabbitkit/core/route.py +329 -0
- rabbitkit-0.9.0/src/rabbitkit/core/router.py +142 -0
- rabbitkit-0.9.0/src/rabbitkit/core/topology.py +261 -0
- rabbitkit-0.9.0/src/rabbitkit/core/topology_dispatch.py +74 -0
- rabbitkit-0.9.0/src/rabbitkit/core/types.py +324 -0
- rabbitkit-0.9.0/src/rabbitkit/dashboard/__init__.py +5 -0
- rabbitkit-0.9.0/src/rabbitkit/dashboard/app.py +212 -0
- rabbitkit-0.9.0/src/rabbitkit/di/__init__.py +19 -0
- rabbitkit-0.9.0/src/rabbitkit/di/context.py +193 -0
- rabbitkit-0.9.0/src/rabbitkit/di/depends.py +42 -0
- rabbitkit-0.9.0/src/rabbitkit/di/resolver.py +503 -0
- rabbitkit-0.9.0/src/rabbitkit/dlq.py +320 -0
- rabbitkit-0.9.0/src/rabbitkit/experimental/__init__.py +50 -0
- rabbitkit-0.9.0/src/rabbitkit/fastapi.py +91 -0
- rabbitkit-0.9.0/src/rabbitkit/health.py +654 -0
- rabbitkit-0.9.0/src/rabbitkit/highload/__init__.py +10 -0
- rabbitkit-0.9.0/src/rabbitkit/highload/backpressure.py +514 -0
- rabbitkit-0.9.0/src/rabbitkit/highload/batch.py +448 -0
- rabbitkit-0.9.0/src/rabbitkit/locking.py +277 -0
- rabbitkit-0.9.0/src/rabbitkit/management.py +470 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/__init__.py +27 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/base.py +125 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/circuit_breaker.py +131 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/compression.py +267 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/deduplication.py +651 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/error_classifier.py +43 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/exception.py +105 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/metrics.py +440 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/otel.py +203 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/rate_limit.py +247 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/retry.py +540 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/signing.py +682 -0
- rabbitkit-0.9.0/src/rabbitkit/middleware/timeout.py +291 -0
- rabbitkit-0.9.0/src/rabbitkit/py.typed +0 -0
- rabbitkit-0.9.0/src/rabbitkit/queue_metrics.py +174 -0
- rabbitkit-0.9.0/src/rabbitkit/results/__init__.py +6 -0
- rabbitkit-0.9.0/src/rabbitkit/results/backend.py +102 -0
- rabbitkit-0.9.0/src/rabbitkit/results/middleware.py +123 -0
- rabbitkit-0.9.0/src/rabbitkit/rpc.py +632 -0
- rabbitkit-0.9.0/src/rabbitkit/serialization/__init__.py +25 -0
- rabbitkit-0.9.0/src/rabbitkit/serialization/base.py +35 -0
- rabbitkit-0.9.0/src/rabbitkit/serialization/json.py +122 -0
- rabbitkit-0.9.0/src/rabbitkit/serialization/msgspec.py +136 -0
- rabbitkit-0.9.0/src/rabbitkit/serialization/pipeline.py +255 -0
- rabbitkit-0.9.0/src/rabbitkit/streams.py +139 -0
- rabbitkit-0.9.0/src/rabbitkit/sync/__init__.py +11 -0
- rabbitkit-0.9.0/src/rabbitkit/sync/batch.py +595 -0
- rabbitkit-0.9.0/src/rabbitkit/sync/broker.py +996 -0
- rabbitkit-0.9.0/src/rabbitkit/sync/connection.py +209 -0
- rabbitkit-0.9.0/src/rabbitkit/sync/pool.py +262 -0
- rabbitkit-0.9.0/src/rabbitkit/sync/transport.py +1085 -0
- rabbitkit-0.9.0/src/rabbitkit/testing/__init__.py +20 -0
- rabbitkit-0.9.0/src/rabbitkit/testing/app.py +99 -0
- rabbitkit-0.9.0/src/rabbitkit/testing/broker.py +540 -0
- rabbitkit-0.9.0/src/rabbitkit/testing/fixtures.py +56 -0
- rabbitkit-0.9.0/src/rabbitkit.egg-info/PKG-INFO +575 -0
- rabbitkit-0.9.0/src/rabbitkit.egg-info/SOURCES.txt +98 -0
- rabbitkit-0.9.0/src/rabbitkit.egg-info/dependency_links.txt +1 -0
- rabbitkit-0.9.0/src/rabbitkit.egg-info/entry_points.txt +2 -0
- rabbitkit-0.9.0/src/rabbitkit.egg-info/requires.txt +68 -0
- rabbitkit-0.9.0/src/rabbitkit.egg-info/top_level.txt +1 -0
rabbitkit-0.9.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Talaat Magdy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rabbitkit-0.9.0/PKG-INFO
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rabbitkit
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Production-grade RabbitMQ toolkit — sync/async, decorator routing, retry, compression, full configurability
|
|
5
|
+
Author-email: Talaat Magdy <talaatmagdy75@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/talaatmagdyx/rabbitkit
|
|
8
|
+
Project-URL: Documentation, https://talaatmagdyx.github.io/rabbitkit/
|
|
9
|
+
Project-URL: Repository, https://github.com/talaatmagdyx/rabbitkit
|
|
10
|
+
Project-URL: Changelog, https://github.com/talaatmagdyx/rabbitkit/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/talaatmagdyx/rabbitkit/issues
|
|
12
|
+
Keywords: rabbitmq,amqp,messaging,pika,aio-pika,microservices,event-driven,message-queue
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: structlog<26.0.0,>=23.1.0
|
|
27
|
+
Provides-Extra: sync
|
|
28
|
+
Requires-Dist: pika<2.0.0,>=1.3.0; extra == "sync"
|
|
29
|
+
Provides-Extra: async
|
|
30
|
+
Requires-Dist: aio-pika<10.0.0,>=9.1.0; extra == "async"
|
|
31
|
+
Provides-Extra: all-brokers
|
|
32
|
+
Requires-Dist: rabbitkit[async,sync]; extra == "all-brokers"
|
|
33
|
+
Provides-Extra: redis
|
|
34
|
+
Requires-Dist: redis<6.0.0,>=5.0.0; extra == "redis"
|
|
35
|
+
Provides-Extra: pydantic
|
|
36
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0; extra == "pydantic"
|
|
37
|
+
Provides-Extra: msgspec
|
|
38
|
+
Requires-Dist: msgspec<1.0.0,>=0.18.0; extra == "msgspec"
|
|
39
|
+
Provides-Extra: fastapi
|
|
40
|
+
Requires-Dist: fastapi<1.0.0,>=0.111.0; extra == "fastapi"
|
|
41
|
+
Provides-Extra: compression
|
|
42
|
+
Requires-Dist: zstandard<1.0.0,>=0.22.0; extra == "compression"
|
|
43
|
+
Provides-Extra: management
|
|
44
|
+
Requires-Dist: aiohttp<4.0.0,>=3.9.0; extra == "management"
|
|
45
|
+
Provides-Extra: otel
|
|
46
|
+
Requires-Dist: opentelemetry-api<2,>=1.20; extra == "otel"
|
|
47
|
+
Provides-Extra: settings
|
|
48
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.0.0; extra == "settings"
|
|
49
|
+
Provides-Extra: cli
|
|
50
|
+
Requires-Dist: typer<1.0.0,>=0.12.0; extra == "cli"
|
|
51
|
+
Provides-Extra: dashboard
|
|
52
|
+
Requires-Dist: starlette<2.0.0,>=0.37.0; extra == "dashboard"
|
|
53
|
+
Requires-Dist: uvicorn<1.0.0,>=0.29.0; extra == "dashboard"
|
|
54
|
+
Provides-Extra: reload
|
|
55
|
+
Requires-Dist: watchfiles<2.0.0,>=0.21.0; extra == "reload"
|
|
56
|
+
Provides-Extra: all
|
|
57
|
+
Requires-Dist: rabbitkit[all-brokers,cli,compression,dashboard,fastapi,management,msgspec,otel,pydantic,redis,reload,settings]; extra == "all"
|
|
58
|
+
Provides-Extra: dev
|
|
59
|
+
Requires-Dist: rabbitkit[all]; extra == "dev"
|
|
60
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
61
|
+
Requires-Dist: httpx<1.0,>=0.27; extra == "dev"
|
|
62
|
+
Requires-Dist: prometheus-client<2.0,>=0.20; extra == "dev"
|
|
63
|
+
Requires-Dist: mkdocs>=1.6.0; extra == "dev"
|
|
64
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "dev"
|
|
65
|
+
Requires-Dist: mkdocstrings[python]>=0.26.0; extra == "dev"
|
|
66
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
67
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
68
|
+
Requires-Dist: pytest-timeout>=2.2; extra == "dev"
|
|
69
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
70
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
71
|
+
Requires-Dist: hypothesis>=6.100.0; extra == "dev"
|
|
72
|
+
Requires-Dist: pre-commit>=3.7.0; extra == "dev"
|
|
73
|
+
Provides-Extra: integration
|
|
74
|
+
Requires-Dist: rabbitkit[dev]; extra == "integration"
|
|
75
|
+
Requires-Dist: testcontainers[rabbitmq]>=4.0.0; extra == "integration"
|
|
76
|
+
Requires-Dist: docker>=7.0.0; extra == "integration"
|
|
77
|
+
Dynamic: license-file
|
|
78
|
+
|
|
79
|
+
<p align="center"><img src="assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
80
|
+
|
|
81
|
+
# rabbitkit
|
|
82
|
+
|
|
83
|
+
**RabbitMQ made enjoyable — less broker plumbing, more business logic.**
|
|
84
|
+
|
|
85
|
+
[](https://pypi.org/project/rabbitkit/)
|
|
86
|
+
[](https://github.com/talaatmagdyx/rabbitkit/actions/workflows/ci.yml)
|
|
87
|
+
[](pyproject.toml)
|
|
88
|
+
[](LICENSE)
|
|
89
|
+
[](pyproject.toml)
|
|
90
|
+
[](pyproject.toml)
|
|
91
|
+
|
|
92
|
+
rabbitkit is a **RabbitMQ-first toolkit for Python services**. It gives you
|
|
93
|
+
clean decorators, safe retries, dead-letter queues, publisher confirms,
|
|
94
|
+
explicit acknowledgement policies, Kubernetes-ready lifecycle hooks,
|
|
95
|
+
structured logging, OpenTelemetry tracing, and real in-memory testing — so
|
|
96
|
+
your team can focus on what each message *means*, not how the broker behaves
|
|
97
|
+
when things fail.
|
|
98
|
+
|
|
99
|
+
RabbitMQ is powerful. Production RabbitMQ is full of sharp edges.
|
|
100
|
+
rabbitkit smooths those edges **without hiding the broker from you**.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from rabbitkit import AsyncBroker, RabbitConfig
|
|
104
|
+
|
|
105
|
+
broker = AsyncBroker(RabbitConfig())
|
|
106
|
+
|
|
107
|
+
@broker.subscriber(queue="orders.created")
|
|
108
|
+
async def handle_order(order: dict) -> None:
|
|
109
|
+
await fulfill_order(order)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
That should feel like application code. The retry topology,
|
|
113
|
+
acknowledgements, confirms, DLQs, shutdown behavior, and test harness should
|
|
114
|
+
not be rewritten in every service. **That is what rabbitkit is for.**
|
|
115
|
+
|
|
116
|
+
**Contents:**
|
|
117
|
+
- [Believes](#what-rabbitkit-believes)
|
|
118
|
+
- [Why](#why-rabbitkit-exists)
|
|
119
|
+
- [Install](#installation)
|
|
120
|
+
- [Quick start](#quick-start)
|
|
121
|
+
- [Safety model](#message-safety-model)
|
|
122
|
+
- [Failure table](#what-happens-when-things-fail)
|
|
123
|
+
- [Ack policies](#acknowledgement-policies)
|
|
124
|
+
- [Production profile](#production-profile)
|
|
125
|
+
- [Observability](#observability)
|
|
126
|
+
- [DI](#dependency-injection)
|
|
127
|
+
- [Middleware](#middleware-batteries-included)
|
|
128
|
+
- [CLI](#operate-it-from-the-terminal)
|
|
129
|
+
- [Where it fits](#where-rabbitkit-fits)
|
|
130
|
+
- [Architecture](#architecture)
|
|
131
|
+
- [Docs](#documentation)
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## What rabbitkit believes
|
|
136
|
+
|
|
137
|
+
Most services need the same things:
|
|
138
|
+
|
|
139
|
+
- a clean way to register consumers
|
|
140
|
+
- safe retry behavior
|
|
141
|
+
- a place for poison messages to go
|
|
142
|
+
- publisher confirms that are **checked**
|
|
143
|
+
- explicit acknowledgement ownership
|
|
144
|
+
- graceful shutdown
|
|
145
|
+
- useful logs and traces
|
|
146
|
+
- health checks that behave correctly in Kubernetes
|
|
147
|
+
- tests that do not require a live broker
|
|
148
|
+
|
|
149
|
+
rabbitkit packages those concerns into one focused toolkit. The philosophy:
|
|
150
|
+
|
|
151
|
+
> **Make RabbitMQ pleasant for developers and predictable for operators.**
|
|
152
|
+
|
|
153
|
+
Developers get a clean programming model. Operators get visible message
|
|
154
|
+
outcomes. Production gets fewer silent failure paths.
|
|
155
|
+
|
|
156
|
+
## Why rabbitkit exists
|
|
157
|
+
|
|
158
|
+
Starting with RabbitMQ is easy: `basic_publish(...)`, `basic_consume(...)`.
|
|
159
|
+
Then production asks better questions:
|
|
160
|
+
|
|
161
|
+
- What happens if a handler fails *forever*?
|
|
162
|
+
- Where does a malformed payload go?
|
|
163
|
+
- Can a rejected message disappear because the queue had no DLX?
|
|
164
|
+
- Did the retry publish **confirm** before the original was acknowledged?
|
|
165
|
+
- Can a DLQ replay remove a message before the republish is confirmed?
|
|
166
|
+
- Can a pod shut down without interrupting in-flight work?
|
|
167
|
+
- Can CI test real consumer behavior without starting RabbitMQ?
|
|
168
|
+
|
|
169
|
+
rabbitkit exists for those questions. Its goal is not to turn RabbitMQ into
|
|
170
|
+
something else — it is to make direct RabbitMQ usage feel like good
|
|
171
|
+
application code: clear routing, safe defaults, explicit outcomes, real
|
|
172
|
+
tests, production-ready lifecycle.
|
|
173
|
+
|
|
174
|
+
**rabbitkit is:**
|
|
175
|
+
|
|
176
|
+
- a RabbitMQ-first toolkit
|
|
177
|
+
- a clean consumer/publisher API
|
|
178
|
+
- a reliability layer over `pika` and `aio-pika`
|
|
179
|
+
- a testing layer for handlers
|
|
180
|
+
- a production lifecycle layer
|
|
181
|
+
- safety defaults for retry, DLQ, confirms, and acks
|
|
182
|
+
|
|
183
|
+
**rabbitkit is not:**
|
|
184
|
+
|
|
185
|
+
- a task queue
|
|
186
|
+
- a scheduler
|
|
187
|
+
- a generic event-streaming abstraction
|
|
188
|
+
- a replacement for understanding RabbitMQ
|
|
189
|
+
- an exactly-once delivery system
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Installation
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pip install rabbitkit[async] # AsyncBroker (aio-pika)
|
|
197
|
+
pip install rabbitkit[sync] # SyncBroker (pika)
|
|
198
|
+
pip install rabbitkit[all-brokers] # both transports
|
|
199
|
+
pip install rabbitkit[all] # everything optional
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Requires Python ≥ 3.11.
|
|
203
|
+
|
|
204
|
+
## The 10-minute path
|
|
205
|
+
|
|
206
|
+
A durable, retrying, DLQ-backed consumer — tested without a broker:
|
|
207
|
+
|
|
208
|
+
1. `pip install rabbitkit[async]`
|
|
209
|
+
2. Create an `AsyncBroker(RabbitConfig())`
|
|
210
|
+
3. Register a handler with `@broker.subscriber(queue=...)`
|
|
211
|
+
4. Add `retry=RetryConfig(max_retries=3, delays=(5, 30, 120))`
|
|
212
|
+
5. Run it: `rabbitkit run myapp.main:broker`
|
|
213
|
+
6. Test it in CI with `TestBroker` — no RabbitMQ required
|
|
214
|
+
|
|
215
|
+
Each step is shown below.
|
|
216
|
+
|
|
217
|
+
## Quick start
|
|
218
|
+
|
|
219
|
+
### 1. Create a consumer
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from rabbitkit import RabbitConfig, AsyncBroker
|
|
223
|
+
|
|
224
|
+
broker = AsyncBroker(RabbitConfig())
|
|
225
|
+
|
|
226
|
+
@broker.subscriber(queue="orders.created")
|
|
227
|
+
async def handle_order(body: dict) -> None:
|
|
228
|
+
print(f"order id={body['id']}")
|
|
229
|
+
|
|
230
|
+
async def main() -> None:
|
|
231
|
+
await broker.start()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
That is enough to consume messages. But production usually needs more than
|
|
235
|
+
"enough".
|
|
236
|
+
|
|
237
|
+
### 2. Publish — and check the outcome
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
async def publish_order() -> None:
|
|
241
|
+
outcome = await broker.publish(
|
|
242
|
+
exchange="orders",
|
|
243
|
+
routing_key="orders.created",
|
|
244
|
+
body={"id": 42, "item": "widget"},
|
|
245
|
+
)
|
|
246
|
+
outcome.raise_for_status()
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
A publish can be `CONFIRMED`, `SENT`, `RETURNED`, `NACKED`, `TIMEOUT`, or
|
|
250
|
+
`ERROR`. Application code can treat those as different states instead of
|
|
251
|
+
assuming "publish called" means "message safe".
|
|
252
|
+
|
|
253
|
+
### 3. Add retry and DLQ handling
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from rabbitkit import RetryConfig
|
|
257
|
+
|
|
258
|
+
@broker.subscriber(
|
|
259
|
+
queue="orders.created",
|
|
260
|
+
exchange="orders",
|
|
261
|
+
routing_key="orders.created",
|
|
262
|
+
retry=RetryConfig(max_retries=3, delays=(5, 30, 120)),
|
|
263
|
+
)
|
|
264
|
+
async def handle_order_with_retry(body: dict) -> None:
|
|
265
|
+
await fulfill_order(body)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This wires the reliability path — **broker-side**, carried in hardened
|
|
269
|
+
headers, surviving crashes and reconnects:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
orders.created
|
|
273
|
+
→ orders.created.retry.1 (5s)
|
|
274
|
+
→ orders.created.retry.2 (30s)
|
|
275
|
+
→ orders.created.retry.3 (120s)
|
|
276
|
+
→ orders.created.dlq
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Transient failures retry with backoff. Permanent failures skip the ladder
|
|
280
|
+
and go straight to the DLQ. By default, rejected messages do not disappear
|
|
281
|
+
silently — every rejecting route gets a DLQ unless you explicitly opt into
|
|
282
|
+
discard behavior.
|
|
283
|
+
|
|
284
|
+
### 4. Test it without RabbitMQ
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
from rabbitkit.testing import TestBroker
|
|
288
|
+
|
|
289
|
+
def test_order_handler():
|
|
290
|
+
broker = TestBroker()
|
|
291
|
+
|
|
292
|
+
@broker.subscriber(queue="orders.created")
|
|
293
|
+
def handle(body: dict) -> None:
|
|
294
|
+
assert body["id"] == 42
|
|
295
|
+
|
|
296
|
+
broker.start()
|
|
297
|
+
broker.publish("orders.created", b'{"id": 42}')
|
|
298
|
+
broker.stop()
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`TestBroker` is not a mock. It runs the real routing, middleware,
|
|
302
|
+
serialization, dependency resolution, settlement, and ack/nack pipeline in
|
|
303
|
+
memory. Your CI can test RabbitMQ behavior without running RabbitMQ.
|
|
304
|
+
|
|
305
|
+
### 5. Run with FastAPI
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from contextlib import asynccontextmanager
|
|
309
|
+
|
|
310
|
+
from fastapi import FastAPI
|
|
311
|
+
|
|
312
|
+
from rabbitkit import RabbitConfig, AsyncBroker
|
|
313
|
+
from rabbitkit.fastapi import rabbitkit_lifespan
|
|
314
|
+
|
|
315
|
+
api_broker = AsyncBroker(RabbitConfig())
|
|
316
|
+
|
|
317
|
+
@api_broker.subscriber(queue="orders.created")
|
|
318
|
+
async def handle_order_event(body: dict) -> None:
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
@asynccontextmanager
|
|
322
|
+
async def lifespan(app: FastAPI):
|
|
323
|
+
async with rabbitkit_lifespan(api_broker):
|
|
324
|
+
yield
|
|
325
|
+
|
|
326
|
+
app = FastAPI(lifespan=lifespan)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Sync example
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
from rabbitkit import RabbitConfig
|
|
333
|
+
from rabbitkit.sync import SyncBroker
|
|
334
|
+
|
|
335
|
+
sync_broker = SyncBroker(RabbitConfig())
|
|
336
|
+
|
|
337
|
+
@sync_broker.subscriber(queue="orders.created")
|
|
338
|
+
def handle_order_sync(body: bytes) -> None:
|
|
339
|
+
print(f"received order: {body!r}")
|
|
340
|
+
|
|
341
|
+
def main() -> None:
|
|
342
|
+
# Blocks until SIGINT/SIGTERM or stop(); reconnects on connection drops
|
|
343
|
+
# and drains in-flight work on pod termination.
|
|
344
|
+
sync_broker.run()
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The sync broker fits simple workers, scripts, legacy services, and teams
|
|
348
|
+
that do not want an asyncio runtime. **Throughput note:** sync confirmed
|
|
349
|
+
publishing waits one confirm per message (~0.9k msg/s measured);
|
|
350
|
+
`worker_count` does not raise it. For high-throughput confirmed publishing
|
|
351
|
+
use `AsyncBroker` + `AsyncBatchPublisher` (pipelined confirms, ~6.1k msg/s
|
|
352
|
+
measured) or `SyncBatchPublisher` (pipelined confirms for sync code on a
|
|
353
|
+
dedicated I/O thread), or scale out across processes.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Message safety model
|
|
358
|
+
|
|
359
|
+
rabbitkit is an **at-least-once** toolkit: a handler may run more than once
|
|
360
|
+
(crash after work but before ack, connection death mid-handler, DLQ replay,
|
|
361
|
+
producer retry after a confirm timeout…). rabbitkit removes dangerous
|
|
362
|
+
ambiguity around those cases — it does not remove the need for idempotency.
|
|
363
|
+
|
|
364
|
+
For payments, emails, tickets, webhooks, external API calls: design the
|
|
365
|
+
handler so running it twice is safe (idempotency keys, unique constraints,
|
|
366
|
+
processed-event tables, outbox patterns — or rabbitkit's deduplication
|
|
367
|
+
middleware, whose `store_results` mode replays the original result to
|
|
368
|
+
duplicates). The rule is simple:
|
|
369
|
+
|
|
370
|
+
> rabbitkit can help you retry safely. Your business logic must still be
|
|
371
|
+
> safe to retry.
|
|
372
|
+
|
|
373
|
+
## What happens when things fail?
|
|
374
|
+
|
|
375
|
+
| Failure mode | rabbitkit behavior |
|
|
376
|
+
|---|---|
|
|
377
|
+
| Handler raises forever | Retry ladder, then DLQ |
|
|
378
|
+
| Malformed payload | Classified permanent, preserved in DLQ |
|
|
379
|
+
| Reject with no DLX | Safe default auto-provisions a DLQ — no silent discard |
|
|
380
|
+
| Retry publish times out | Original is **not** acked as if the retry succeeded |
|
|
381
|
+
| DLQ replay publish fails | DLQ message is **not** removed as if replay succeeded |
|
|
382
|
+
| Message unroutable | Mandatory publishing returns a distinct `RETURNED` outcome |
|
|
383
|
+
| Broker blips | Readiness changes; liveness does **not** kill the pod |
|
|
384
|
+
| Pod gets SIGTERM | Consumers stop first, in-flight work drains |
|
|
385
|
+
| CI has no RabbitMQ | `TestBroker` runs the real pipeline in memory |
|
|
386
|
+
|
|
387
|
+
## Acknowledgement policies
|
|
388
|
+
|
|
389
|
+
Settlement is a decision, not a side effect hidden in a callback.
|
|
390
|
+
|
|
391
|
+
| Policy | Behavior | Use case |
|
|
392
|
+
|---|---|---|
|
|
393
|
+
| `AUTO` | Ack on success, retry/reject on failure | Most consumers |
|
|
394
|
+
| `MANUAL` | Handler owns ack/nack/reject | Custom settlement flows |
|
|
395
|
+
| `NACK_ON_ERROR` | Ack on success, nack on failure | Never silently accept failed work |
|
|
396
|
+
| `ACK_FIRST` | Ack before the handler runs | At-most-once workloads |
|
|
397
|
+
|
|
398
|
+
`ACK_FIRST` can lose messages if the handler fails after the ack — use it
|
|
399
|
+
only when loss is acceptable.
|
|
400
|
+
|
|
401
|
+
## Production profile
|
|
402
|
+
|
|
403
|
+
The recommended baseline (see the
|
|
404
|
+
[production checklist](docs/production/checklist.md)): quorum queues (+
|
|
405
|
+
`delivery_limit`), per-queue retry/DLQ topology, publisher confirms on,
|
|
406
|
+
mandatory publishing where routing matters, checked `PublishOutcome`s,
|
|
407
|
+
explicit ack policies, structured logs, split readiness/liveness probes,
|
|
408
|
+
management-API metrics for queue depth and consumer lag, idempotent
|
|
409
|
+
handlers. Migrating existing classic queues to quorum? There's a tool:
|
|
410
|
+
`rabbitkit topology migrate` ([guide](docs/quorum-migration.md)).
|
|
411
|
+
|
|
412
|
+
## Observability
|
|
413
|
+
|
|
414
|
+
Structured logs carry message context (`message_id`, `correlation_id`,
|
|
415
|
+
routing, queue, handler, retry count, settlement, duration, error type) with
|
|
416
|
+
secret redaction on by default. Metrics cover consumed/acked/nacked/
|
|
417
|
+
retried/dead-lettered counts, publish outcomes, handler latency,
|
|
418
|
+
redeliveries, reconnects, and — via the management API poller — queue depth
|
|
419
|
+
and consumer lag. Tracing is standard OpenTelemetry
|
|
420
|
+
(`pip install rabbitkit[otel]`): W3C context propagation over AMQP headers,
|
|
421
|
+
one continuous trace from publish to consume.
|
|
422
|
+
|
|
423
|
+
## Advanced & experimental
|
|
424
|
+
|
|
425
|
+
**Advanced stable** (enable deliberately): publish-side backpressure
|
|
426
|
+
(`FlowController`), batch publishing/acking, pipelined sync confirms
|
|
427
|
+
(`SyncBatchPublisher`), DLQ inspector + replay CLI, management API client,
|
|
428
|
+
topology validation/drift/migration CLI, health watcher, circuit-breaker
|
|
429
|
+
middleware (bring any `CircuitBreakerProtocol` implementation, e.g.
|
|
430
|
+
pybreaker).
|
|
431
|
+
|
|
432
|
+
**Experimental** (may change without a deprecation cycle — read the
|
|
433
|
+
[stability policy](docs/stability-policy.md)): RPC over direct reply-to,
|
|
434
|
+
distributed locking, message signing, result backends, stream queues, the
|
|
435
|
+
monitoring dashboard. Notable caveats: the default signing nonce cache is
|
|
436
|
+
per-process (use a shared cache for real replay protection), and never
|
|
437
|
+
expose the dashboard publicly without authentication.
|
|
438
|
+
|
|
439
|
+
## Dependency injection
|
|
440
|
+
|
|
441
|
+
Handlers resolve request-like context declaratively — typed body, headers,
|
|
442
|
+
routing-key segments, and shared dependencies:
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
from rabbitkit import AsyncBroker, Context, Depends, Header, Path, RabbitConfig
|
|
446
|
+
from rabbitkit.core.message import RabbitMessage
|
|
447
|
+
|
|
448
|
+
di_broker = AsyncBroker(RabbitConfig())
|
|
449
|
+
|
|
450
|
+
def get_db() -> str:
|
|
451
|
+
return "db-connection"
|
|
452
|
+
|
|
453
|
+
@di_broker.subscriber(queue="tenants.{tenant_id}.orders")
|
|
454
|
+
async def handle_tenant_order(
|
|
455
|
+
body: dict,
|
|
456
|
+
tenant_id: str = Path(),
|
|
457
|
+
trace_id: str = Header("x-trace-id", default=""),
|
|
458
|
+
db: str = Depends(get_db),
|
|
459
|
+
message: RabbitMessage = Context(),
|
|
460
|
+
) -> None:
|
|
461
|
+
...
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Serialization is pluggable per route: raw bytes, JSON (default), Pydantic
|
|
465
|
+
models, msgspec structs, or a custom parser/decoder pipeline — annotate the
|
|
466
|
+
body parameter with the type you want and pick the serializer that
|
|
467
|
+
validates it.
|
|
468
|
+
|
|
469
|
+
## Middleware, batteries included
|
|
470
|
+
|
|
471
|
+
| Middleware | Job |
|
|
472
|
+
|---|---|
|
|
473
|
+
| `RetryMiddleware` | Broker-side retry ladder (auto-wired with `retry=`) |
|
|
474
|
+
| `DeduplicationMiddleware` | Redis-backed duplicate suppression; `claim` policy is crash-safe; `store_results` replays the original answer to duplicates |
|
|
475
|
+
| `MetricsMiddleware` | Counters + latency histograms, cardinality-guarded labels |
|
|
476
|
+
| `OTelTracingMiddleware` | Standard OpenTelemetry spans + W3C propagation |
|
|
477
|
+
| `CompressionMiddleware` | gzip/zstd with streaming zip-bomb guards |
|
|
478
|
+
| `RateLimitMiddleware` | Token-bucket consume throttling (nack/drop/wait) |
|
|
479
|
+
| `TimeoutMiddleware` | Per-handler deadlines, retry-classified |
|
|
480
|
+
| `CircuitBreakerMiddleware` | Wraps any `CircuitBreakerProtocol` implementation |
|
|
481
|
+
| `SigningMiddleware` | HMAC signing + replay protection (experimental) |
|
|
482
|
+
|
|
483
|
+
## Operate it from the terminal
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
rabbitkit run myapp.main:broker # run consumers
|
|
487
|
+
rabbitkit dlq inspect orders.dlq # peek at poison messages
|
|
488
|
+
rabbitkit dlq replay orders.dlq orders --reset-retry-count
|
|
489
|
+
rabbitkit topology validate myapp.main:broker # declared vs live drift
|
|
490
|
+
rabbitkit topology migrate myapp.main:broker # classic -> quorum, planned & resumable
|
|
491
|
+
rabbitkit health myapp.main:broker
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
The DLQ replay acks a message only after its republish is broker-confirmed —
|
|
495
|
+
the recovery tool cannot itself lose messages.
|
|
496
|
+
|
|
497
|
+
<p align="center"><img src="assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
498
|
+
|
|
499
|
+
## Where rabbitkit fits
|
|
500
|
+
|
|
501
|
+
rabbitkit sits *above* `pika` and `aio-pika` — a reliability layer, not a
|
|
502
|
+
replacement; drop to the underlying client any time. It is for teams that
|
|
503
|
+
use RabbitMQ directly and want production-safe messaging without rebuilding
|
|
504
|
+
retries, DLQs, publisher confirms, acknowledgements, lifecycle handling, and
|
|
505
|
+
test infrastructure in every service.
|
|
506
|
+
|
|
507
|
+
**A good fit when:**
|
|
508
|
+
|
|
509
|
+
- RabbitMQ is your primary broker
|
|
510
|
+
- message loss would be an incident
|
|
511
|
+
- retry and DLQ behavior must be explicit
|
|
512
|
+
- CI should test handlers without a real broker
|
|
513
|
+
- Kubernetes shutdown and readiness matter
|
|
514
|
+
- operators need visibility into message outcomes
|
|
515
|
+
|
|
516
|
+
**Probably not the right fit when:**
|
|
517
|
+
|
|
518
|
+
- you need a task queue or scheduler (use a task framework)
|
|
519
|
+
- you need a broker-agnostic framework or want to hide RabbitMQ semantics
|
|
520
|
+
- at-most-once behavior is acceptable and a raw client is enough
|
|
521
|
+
|
|
522
|
+
A detailed framework-by-framework comparison lives in
|
|
523
|
+
[docs/comparison.md](docs/comparison.md).
|
|
524
|
+
|
|
525
|
+
## Architecture
|
|
526
|
+
|
|
527
|
+
```
|
|
528
|
+
rabbitkit/
|
|
529
|
+
core/ # route registry, topology, pipeline, settlement, config
|
|
530
|
+
sync/ # pika adapter (+ SyncBatchPublisher)
|
|
531
|
+
async_/ # aio-pika adapter (+ AsyncBatchPublisher)
|
|
532
|
+
middleware/ # retry, dedup, metrics, otel, compression, rate limit…
|
|
533
|
+
serialization/ # JSON, msgspec, Pydantic, parser/decoder pipeline
|
|
534
|
+
di/ # Depends, Header, Path, Context
|
|
535
|
+
testing/ # TestBroker and friends
|
|
536
|
+
highload/ # FlowController, BatchPublisher, BatchAcker
|
|
537
|
+
cli/ # dlq, topology, migrate, health, run, shell
|
|
538
|
+
fastapi.py # FastAPI lifespan integration
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
The shared core has **zero** imports from `pika` or `aio-pika` — both
|
|
542
|
+
transports are adapters over the same registry, pipeline, topology model,
|
|
543
|
+
and settlement rules.
|
|
544
|
+
|
|
545
|
+
## Compatibility
|
|
546
|
+
|
|
547
|
+
Python ≥ 3.11 (tested: 3.11 / 3.12 / 3.13 / 3.14; 3.15 pre-release experimental) · RabbitMQ ≥ 3.12 recommended ·
|
|
548
|
+
`pika >= 1.3, < 2.0` · `aio-pika >= 9.1, < 10.0`
|
|
549
|
+
|
|
550
|
+
## Documentation
|
|
551
|
+
|
|
552
|
+
**📚 Full rendered docs: [talaatmagdyx.github.io/rabbitkit](https://talaatmagdyx.github.io/rabbitkit/)**
|
|
553
|
+
|
|
554
|
+
- [Getting Started](docs/guide/getting-started.md)
|
|
555
|
+
- [Full Guide](docs/guide/full-guide.md)
|
|
556
|
+
- [Message Safety](docs/message-safety.md)
|
|
557
|
+
- [Retry & DLQ](docs/retry-and-dlq.md)
|
|
558
|
+
- [Production Checklist](docs/production/checklist.md)
|
|
559
|
+
- [Idempotency Contract](docs/production/idempotency.md)
|
|
560
|
+
- [Kubernetes](docs/kubernetes.md)
|
|
561
|
+
- [Quorum Migration](docs/quorum-migration.md)
|
|
562
|
+
- [Security](docs/security.md)
|
|
563
|
+
- [Stability Policy](docs/stability-policy.md)
|
|
564
|
+
- [Troubleshooting](docs/troubleshooting.md)
|
|
565
|
+
|
|
566
|
+
## Contributing & security
|
|
567
|
+
|
|
568
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development and quality
|
|
569
|
+
gates (ruff, `mypy --strict`, near-total test coverage — the bar is real).
|
|
570
|
+
Found a vulnerability? Follow [SECURITY.md](SECURITY.md) and report it
|
|
571
|
+
privately.
|
|
572
|
+
|
|
573
|
+
## License
|
|
574
|
+
|
|
575
|
+
[MIT](LICENSE)
|