rabbitkit 0.9.0__tar.gz → 0.9.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.
- {rabbitkit-0.9.0/src/rabbitkit.egg-info → rabbitkit-0.9.1}/PKG-INFO +28 -25
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/README.md +26 -24
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/pyproject.toml +11 -1
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/_version.py +1 -1
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/async_/broker.py +11 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/pipeline.py +72 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/sync/broker.py +11 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/sync/transport.py +16 -1
- {rabbitkit-0.9.0 → rabbitkit-0.9.1/src/rabbitkit.egg-info}/PKG-INFO +28 -25
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit.egg-info/requires.txt +1 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/LICENSE +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/setup.cfg +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/aio/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/async_/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/async_/batch.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/async_/connection.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/async_/pool.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/async_/transport.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/asyncapi/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/asyncapi/generator.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/asyncapi/schema.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/_utils.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/dlq.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/health.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/migrate.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/routes.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/run.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/shell.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/cli/commands/topology.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/concurrency.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/app.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/config.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/env_config.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/errors.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/logging.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/message.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/path.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/protocols.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/registry.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/route.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/router.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/topology.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/topology_dispatch.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/core/types.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/dashboard/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/dashboard/app.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/di/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/di/context.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/di/depends.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/di/resolver.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/dlq.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/experimental/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/fastapi.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/health.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/highload/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/highload/backpressure.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/highload/batch.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/locking.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/management.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/base.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/circuit_breaker.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/compression.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/deduplication.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/error_classifier.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/exception.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/metrics.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/otel.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/rate_limit.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/retry.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/signing.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/middleware/timeout.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/py.typed +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/queue_metrics.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/results/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/results/backend.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/results/middleware.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/rpc.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/serialization/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/serialization/base.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/serialization/json.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/serialization/msgspec.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/serialization/pipeline.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/streams.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/sync/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/sync/batch.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/sync/connection.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/sync/pool.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/testing/__init__.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/testing/app.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/testing/broker.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit/testing/fixtures.py +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit.egg-info/SOURCES.txt +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit.egg-info/dependency_links.txt +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit.egg-info/entry_points.txt +0 -0
- {rabbitkit-0.9.0 → rabbitkit-0.9.1}/src/rabbitkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rabbitkit
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Production-grade RabbitMQ toolkit — sync/async, decorator routing, retry, compression, full configurability
|
|
5
5
|
Author-email: Talaat Magdy <talaatmagdy75@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -60,6 +60,7 @@ Requires-Dist: rabbitkit[all]; extra == "dev"
|
|
|
60
60
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
61
61
|
Requires-Dist: httpx<1.0,>=0.27; extra == "dev"
|
|
62
62
|
Requires-Dist: prometheus-client<2.0,>=0.20; extra == "dev"
|
|
63
|
+
Requires-Dist: psutil>=5.9; extra == "dev"
|
|
63
64
|
Requires-Dist: mkdocs>=1.6.0; extra == "dev"
|
|
64
65
|
Requires-Dist: mkdocs-material>=9.5.0; extra == "dev"
|
|
65
66
|
Requires-Dist: mkdocstrings[python]>=0.26.0; extra == "dev"
|
|
@@ -76,7 +77,7 @@ Requires-Dist: testcontainers[rabbitmq]>=4.0.0; extra == "integration"
|
|
|
76
77
|
Requires-Dist: docker>=7.0.0; extra == "integration"
|
|
77
78
|
Dynamic: license-file
|
|
78
79
|
|
|
79
|
-
<p align="center"><img src="assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
80
|
+
<p align="center"><img src="https://raw.githubusercontent.com/talaatmagdyx/rabbitkit/main/assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
80
81
|
|
|
81
82
|
# rabbitkit
|
|
82
83
|
|
|
@@ -84,10 +85,10 @@ Dynamic: license-file
|
|
|
84
85
|
|
|
85
86
|
[](https://pypi.org/project/rabbitkit/)
|
|
86
87
|
[](https://github.com/talaatmagdyx/rabbitkit/actions/workflows/ci.yml)
|
|
87
|
-
[](pyproject.toml)
|
|
88
|
-
[](LICENSE)
|
|
89
|
-
[](pyproject.toml)
|
|
90
|
-
[](pyproject.toml)
|
|
88
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
89
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/LICENSE)
|
|
90
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
91
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
91
92
|
|
|
92
93
|
rabbitkit is a **RabbitMQ-first toolkit for Python services**. It gives you
|
|
93
94
|
clean decorators, safe retries, dead-letter queues, publisher confirms,
|
|
@@ -192,6 +193,8 @@ tests, production-ready lifecycle.
|
|
|
192
193
|
|
|
193
194
|
## Installation
|
|
194
195
|
|
|
196
|
+
Available on PyPI: **[pypi.org/project/rabbitkit](https://pypi.org/project/rabbitkit/)**
|
|
197
|
+
|
|
195
198
|
```bash
|
|
196
199
|
pip install rabbitkit[async] # AsyncBroker (aio-pika)
|
|
197
200
|
pip install rabbitkit[sync] # SyncBroker (pika)
|
|
@@ -401,13 +404,13 @@ only when loss is acceptable.
|
|
|
401
404
|
## Production profile
|
|
402
405
|
|
|
403
406
|
The recommended baseline (see the
|
|
404
|
-
[production checklist](docs/production/checklist.md)): quorum queues (+
|
|
407
|
+
[production checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)): quorum queues (+
|
|
405
408
|
`delivery_limit`), per-queue retry/DLQ topology, publisher confirms on,
|
|
406
409
|
mandatory publishing where routing matters, checked `PublishOutcome`s,
|
|
407
410
|
explicit ack policies, structured logs, split readiness/liveness probes,
|
|
408
411
|
management-API metrics for queue depth and consumer lag, idempotent
|
|
409
412
|
handlers. Migrating existing classic queues to quorum? There's a tool:
|
|
410
|
-
`rabbitkit topology migrate` ([guide](docs/quorum-migration.md)).
|
|
413
|
+
`rabbitkit topology migrate` ([guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/quorum-migration.md)).
|
|
411
414
|
|
|
412
415
|
## Observability
|
|
413
416
|
|
|
@@ -430,7 +433,7 @@ middleware (bring any `CircuitBreakerProtocol` implementation, e.g.
|
|
|
430
433
|
pybreaker).
|
|
431
434
|
|
|
432
435
|
**Experimental** (may change without a deprecation cycle — read the
|
|
433
|
-
[stability policy](docs/stability-policy.md)): RPC over direct reply-to,
|
|
436
|
+
[stability policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)): RPC over direct reply-to,
|
|
434
437
|
distributed locking, message signing, result backends, stream queues, the
|
|
435
438
|
monitoring dashboard. Notable caveats: the default signing nonce cache is
|
|
436
439
|
per-process (use a shared cache for real replay protection), and never
|
|
@@ -494,7 +497,7 @@ rabbitkit health myapp.main:broker
|
|
|
494
497
|
The DLQ replay acks a message only after its republish is broker-confirmed —
|
|
495
498
|
the recovery tool cannot itself lose messages.
|
|
496
499
|
|
|
497
|
-
<p align="center"><img src="assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
500
|
+
<p align="center"><img src="https://raw.githubusercontent.com/talaatmagdyx/rabbitkit/main/assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
498
501
|
|
|
499
502
|
## Where rabbitkit fits
|
|
500
503
|
|
|
@@ -520,7 +523,7 @@ test infrastructure in every service.
|
|
|
520
523
|
- at-most-once behavior is acceptable and a raw client is enough
|
|
521
524
|
|
|
522
525
|
A detailed framework-by-framework comparison lives in
|
|
523
|
-
[docs/comparison.md](docs/comparison.md).
|
|
526
|
+
[docs/comparison.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/comparison.md).
|
|
524
527
|
|
|
525
528
|
## Architecture
|
|
526
529
|
|
|
@@ -551,25 +554,25 @@ Python ≥ 3.11 (tested: 3.11 / 3.12 / 3.13 / 3.14; 3.15 pre-release experimenta
|
|
|
551
554
|
|
|
552
555
|
**📚 Full rendered docs: [talaatmagdyx.github.io/rabbitkit](https://talaatmagdyx.github.io/rabbitkit/)**
|
|
553
556
|
|
|
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)
|
|
557
|
+
- [Getting Started](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/getting-started.md)
|
|
558
|
+
- [Full Guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/full-guide.md)
|
|
559
|
+
- [Message Safety](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/message-safety.md)
|
|
560
|
+
- [Retry & DLQ](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/retry-and-dlq.md)
|
|
561
|
+
- [Production Checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)
|
|
562
|
+
- [Idempotency Contract](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/idempotency.md)
|
|
563
|
+
- [Kubernetes](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/kubernetes.md)
|
|
564
|
+
- [Quorum Migration](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/quorum-migration.md)
|
|
565
|
+
- [Security](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/security.md)
|
|
566
|
+
- [Stability Policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)
|
|
567
|
+
- [Troubleshooting](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/troubleshooting.md)
|
|
565
568
|
|
|
566
569
|
## Contributing & security
|
|
567
570
|
|
|
568
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development and quality
|
|
571
|
+
See [CONTRIBUTING.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/CONTRIBUTING.md) for local development and quality
|
|
569
572
|
gates (ruff, `mypy --strict`, near-total test coverage — the bar is real).
|
|
570
|
-
Found a vulnerability? Follow [SECURITY.md](SECURITY.md) and report it
|
|
573
|
+
Found a vulnerability? Follow [SECURITY.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/SECURITY.md) and report it
|
|
571
574
|
privately.
|
|
572
575
|
|
|
573
576
|
## License
|
|
574
577
|
|
|
575
|
-
[MIT](LICENSE)
|
|
578
|
+
[MIT](https://github.com/talaatmagdyx/rabbitkit/blob/main/LICENSE)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<p align="center"><img src="assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/talaatmagdyx/rabbitkit/main/assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
2
2
|
|
|
3
3
|
# rabbitkit
|
|
4
4
|
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://pypi.org/project/rabbitkit/)
|
|
8
8
|
[](https://github.com/talaatmagdyx/rabbitkit/actions/workflows/ci.yml)
|
|
9
|
-
[](pyproject.toml)
|
|
10
|
-
[](LICENSE)
|
|
11
|
-
[](pyproject.toml)
|
|
12
|
-
[](pyproject.toml)
|
|
9
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
10
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/LICENSE)
|
|
11
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
12
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
13
13
|
|
|
14
14
|
rabbitkit is a **RabbitMQ-first toolkit for Python services**. It gives you
|
|
15
15
|
clean decorators, safe retries, dead-letter queues, publisher confirms,
|
|
@@ -114,6 +114,8 @@ tests, production-ready lifecycle.
|
|
|
114
114
|
|
|
115
115
|
## Installation
|
|
116
116
|
|
|
117
|
+
Available on PyPI: **[pypi.org/project/rabbitkit](https://pypi.org/project/rabbitkit/)**
|
|
118
|
+
|
|
117
119
|
```bash
|
|
118
120
|
pip install rabbitkit[async] # AsyncBroker (aio-pika)
|
|
119
121
|
pip install rabbitkit[sync] # SyncBroker (pika)
|
|
@@ -323,13 +325,13 @@ only when loss is acceptable.
|
|
|
323
325
|
## Production profile
|
|
324
326
|
|
|
325
327
|
The recommended baseline (see the
|
|
326
|
-
[production checklist](docs/production/checklist.md)): quorum queues (+
|
|
328
|
+
[production checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)): quorum queues (+
|
|
327
329
|
`delivery_limit`), per-queue retry/DLQ topology, publisher confirms on,
|
|
328
330
|
mandatory publishing where routing matters, checked `PublishOutcome`s,
|
|
329
331
|
explicit ack policies, structured logs, split readiness/liveness probes,
|
|
330
332
|
management-API metrics for queue depth and consumer lag, idempotent
|
|
331
333
|
handlers. Migrating existing classic queues to quorum? There's a tool:
|
|
332
|
-
`rabbitkit topology migrate` ([guide](docs/quorum-migration.md)).
|
|
334
|
+
`rabbitkit topology migrate` ([guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/quorum-migration.md)).
|
|
333
335
|
|
|
334
336
|
## Observability
|
|
335
337
|
|
|
@@ -352,7 +354,7 @@ middleware (bring any `CircuitBreakerProtocol` implementation, e.g.
|
|
|
352
354
|
pybreaker).
|
|
353
355
|
|
|
354
356
|
**Experimental** (may change without a deprecation cycle — read the
|
|
355
|
-
[stability policy](docs/stability-policy.md)): RPC over direct reply-to,
|
|
357
|
+
[stability policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)): RPC over direct reply-to,
|
|
356
358
|
distributed locking, message signing, result backends, stream queues, the
|
|
357
359
|
monitoring dashboard. Notable caveats: the default signing nonce cache is
|
|
358
360
|
per-process (use a shared cache for real replay protection), and never
|
|
@@ -416,7 +418,7 @@ rabbitkit health myapp.main:broker
|
|
|
416
418
|
The DLQ replay acks a message only after its republish is broker-confirmed —
|
|
417
419
|
the recovery tool cannot itself lose messages.
|
|
418
420
|
|
|
419
|
-
<p align="center"><img src="assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
421
|
+
<p align="center"><img src="https://raw.githubusercontent.com/talaatmagdyx/rabbitkit/main/assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
420
422
|
|
|
421
423
|
## Where rabbitkit fits
|
|
422
424
|
|
|
@@ -442,7 +444,7 @@ test infrastructure in every service.
|
|
|
442
444
|
- at-most-once behavior is acceptable and a raw client is enough
|
|
443
445
|
|
|
444
446
|
A detailed framework-by-framework comparison lives in
|
|
445
|
-
[docs/comparison.md](docs/comparison.md).
|
|
447
|
+
[docs/comparison.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/comparison.md).
|
|
446
448
|
|
|
447
449
|
## Architecture
|
|
448
450
|
|
|
@@ -473,25 +475,25 @@ Python ≥ 3.11 (tested: 3.11 / 3.12 / 3.13 / 3.14; 3.15 pre-release experimenta
|
|
|
473
475
|
|
|
474
476
|
**📚 Full rendered docs: [talaatmagdyx.github.io/rabbitkit](https://talaatmagdyx.github.io/rabbitkit/)**
|
|
475
477
|
|
|
476
|
-
- [Getting Started](docs/guide/getting-started.md)
|
|
477
|
-
- [Full Guide](docs/guide/full-guide.md)
|
|
478
|
-
- [Message Safety](docs/message-safety.md)
|
|
479
|
-
- [Retry & DLQ](docs/retry-and-dlq.md)
|
|
480
|
-
- [Production Checklist](docs/production/checklist.md)
|
|
481
|
-
- [Idempotency Contract](docs/production/idempotency.md)
|
|
482
|
-
- [Kubernetes](docs/kubernetes.md)
|
|
483
|
-
- [Quorum Migration](docs/quorum-migration.md)
|
|
484
|
-
- [Security](docs/security.md)
|
|
485
|
-
- [Stability Policy](docs/stability-policy.md)
|
|
486
|
-
- [Troubleshooting](docs/troubleshooting.md)
|
|
478
|
+
- [Getting Started](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/getting-started.md)
|
|
479
|
+
- [Full Guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/full-guide.md)
|
|
480
|
+
- [Message Safety](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/message-safety.md)
|
|
481
|
+
- [Retry & DLQ](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/retry-and-dlq.md)
|
|
482
|
+
- [Production Checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)
|
|
483
|
+
- [Idempotency Contract](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/idempotency.md)
|
|
484
|
+
- [Kubernetes](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/kubernetes.md)
|
|
485
|
+
- [Quorum Migration](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/quorum-migration.md)
|
|
486
|
+
- [Security](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/security.md)
|
|
487
|
+
- [Stability Policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)
|
|
488
|
+
- [Troubleshooting](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/troubleshooting.md)
|
|
487
489
|
|
|
488
490
|
## Contributing & security
|
|
489
491
|
|
|
490
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development and quality
|
|
492
|
+
See [CONTRIBUTING.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/CONTRIBUTING.md) for local development and quality
|
|
491
493
|
gates (ruff, `mypy --strict`, near-total test coverage — the bar is real).
|
|
492
|
-
Found a vulnerability? Follow [SECURITY.md](SECURITY.md) and report it
|
|
494
|
+
Found a vulnerability? Follow [SECURITY.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/SECURITY.md) and report it
|
|
493
495
|
privately.
|
|
494
496
|
|
|
495
497
|
## License
|
|
496
498
|
|
|
497
|
-
[MIT](LICENSE)
|
|
499
|
+
[MIT](https://github.com/talaatmagdyx/rabbitkit/blob/main/LICENSE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rabbitkit"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.1"
|
|
4
4
|
description = "Production-grade RabbitMQ toolkit — sync/async, decorator routing, retry, compression, full configurability"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -50,6 +50,7 @@ dev = [
|
|
|
50
50
|
"pytest>=8.0",
|
|
51
51
|
"httpx>=0.27,<1.0", # starlette TestClient (dashboard tests)
|
|
52
52
|
"prometheus-client>=0.20,<2.0", # PrometheusCollector tests
|
|
53
|
+
"psutil>=5.9", # bench_resources (memory/FD tracking)
|
|
53
54
|
"mkdocs>=1.6.0",
|
|
54
55
|
"mkdocs-material>=9.5.0",
|
|
55
56
|
"mkdocstrings[python]>=0.26.0",
|
|
@@ -138,3 +139,12 @@ warn_unused_configs = true
|
|
|
138
139
|
asyncio_mode = "auto"
|
|
139
140
|
testpaths = ["tests"]
|
|
140
141
|
markers = ["integration: integration tests (may need RabbitMQ)"]
|
|
142
|
+
filterwarnings = [
|
|
143
|
+
# rabbitkit's own operator-guidance warnings, exercised deliberately by
|
|
144
|
+
# tests (dedicated pytest.warns tests still assert they fire — pytest.warns
|
|
145
|
+
# bypasses these filters). Real users still see them.
|
|
146
|
+
"ignore:Starting a single-worker sync consumer:RuntimeWarning",
|
|
147
|
+
"ignore:SigningMiddleware is using the default in-memory:RuntimeWarning",
|
|
148
|
+
# third-party: testcontainers' own deprecated decorator
|
|
149
|
+
"ignore:The @wait_container_is_ready decorator is deprecated:DeprecationWarning",
|
|
150
|
+
]
|
|
@@ -219,6 +219,17 @@ class AsyncBroker:
|
|
|
219
219
|
if self._in_flight == 0:
|
|
220
220
|
cond.notify_all()
|
|
221
221
|
|
|
222
|
+
@property
|
|
223
|
+
def started(self) -> bool:
|
|
224
|
+
"""True between a successful ``start()`` and ``stop()``.
|
|
225
|
+
|
|
226
|
+
Public counterpart of ``_started`` — health checks
|
|
227
|
+
(:func:`rabbitkit.health.broker_health_check`) read this instead of
|
|
228
|
+
falling back to the private attribute (which emits a
|
|
229
|
+
DeprecationWarning).
|
|
230
|
+
"""
|
|
231
|
+
return self._started
|
|
232
|
+
|
|
222
233
|
@property
|
|
223
234
|
def config(self) -> RabbitConfig:
|
|
224
235
|
return self._config
|
|
@@ -37,6 +37,37 @@ from rabbitkit.serialization.base import Serializer
|
|
|
37
37
|
logger = structlog.stdlib.get_logger(__name__)
|
|
38
38
|
_stdlib_logger = logging.getLogger(__name__)
|
|
39
39
|
|
|
40
|
+
# Transport "channel/connection died" exception class names. Matched by NAME
|
|
41
|
+
# (not isinstance) because core/ never imports pika or aio-pika. Covers pika
|
|
42
|
+
# (ChannelWrongStateError, ChannelClosed*, ConnectionClosed*, StreamLostError)
|
|
43
|
+
# and aio-pika/aiormq (ChannelInvalidStateError, AMQPConnectionError).
|
|
44
|
+
_CHANNEL_GONE_NAMES = frozenset(
|
|
45
|
+
{
|
|
46
|
+
"ChannelWrongStateError",
|
|
47
|
+
"ChannelClosed",
|
|
48
|
+
"ChannelClosedByBroker",
|
|
49
|
+
"ChannelClosedByClient",
|
|
50
|
+
"ChannelInvalidStateError",
|
|
51
|
+
"ConnectionClosed",
|
|
52
|
+
"ConnectionClosedByBroker",
|
|
53
|
+
"ConnectionWrongStateError",
|
|
54
|
+
"AMQPConnectionError",
|
|
55
|
+
"StreamLostError",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_channel_gone(exc: BaseException) -> bool:
|
|
61
|
+
"""True if *exc* (or its cause chain) is a transport channel/connection-death error."""
|
|
62
|
+
seen: set[int] = set()
|
|
63
|
+
current: BaseException | None = exc
|
|
64
|
+
while current is not None and id(current) not in seen:
|
|
65
|
+
if type(current).__name__ in _CHANNEL_GONE_NAMES:
|
|
66
|
+
return True
|
|
67
|
+
seen.add(id(current))
|
|
68
|
+
current = current.__cause__ or current.__context__
|
|
69
|
+
return False
|
|
70
|
+
|
|
40
71
|
# M10: on the async path, bodies at/above this size are decoded in a worker
|
|
41
72
|
# thread (asyncio.to_thread) so a large JSON/msgspec/pydantic parse doesn't
|
|
42
73
|
# block the event loop. Below it, inline decode is faster than the thread hop.
|
|
@@ -1220,6 +1251,28 @@ class HandlerPipeline:
|
|
|
1220
1251
|
exc: Exception,
|
|
1221
1252
|
) -> None:
|
|
1222
1253
|
"""Handle exception in sync pipeline per AckPolicy (Contract 1)."""
|
|
1254
|
+
try:
|
|
1255
|
+
self._handle_sync_exception_inner(route, message, exc)
|
|
1256
|
+
except Exception as settle_exc:
|
|
1257
|
+
# The settlement attempt itself failed because the channel or
|
|
1258
|
+
# connection died (SIGTERM drain, broker restart, network cut).
|
|
1259
|
+
# Nothing further can be settled on a dead channel and the broker
|
|
1260
|
+
# will redeliver the unacked message — warn instead of letting a
|
|
1261
|
+
# secondary exception escape as a full ERROR traceback.
|
|
1262
|
+
if not _is_channel_gone(settle_exc):
|
|
1263
|
+
raise
|
|
1264
|
+
logger.warning(
|
|
1265
|
+
"Channel closed before settlement (%s); leaving message "
|
|
1266
|
+
"unsettled — the broker will redeliver it (at-least-once)",
|
|
1267
|
+
type(settle_exc).__name__,
|
|
1268
|
+
)
|
|
1269
|
+
|
|
1270
|
+
def _handle_sync_exception_inner(
|
|
1271
|
+
self,
|
|
1272
|
+
route: RouteDefinition,
|
|
1273
|
+
message: RabbitMessage,
|
|
1274
|
+
exc: Exception,
|
|
1275
|
+
) -> None:
|
|
1223
1276
|
if message.is_settled:
|
|
1224
1277
|
# Already settled (e.g., MANUAL mode handler settled then raised)
|
|
1225
1278
|
logger.warning("Exception after settlement: %s", exc)
|
|
@@ -1261,6 +1314,25 @@ class HandlerPipeline:
|
|
|
1261
1314
|
exc: Exception,
|
|
1262
1315
|
) -> None:
|
|
1263
1316
|
"""Handle exception in async pipeline per AckPolicy (Contract 1)."""
|
|
1317
|
+
try:
|
|
1318
|
+
await self._handle_async_exception_inner(route, message, exc)
|
|
1319
|
+
except Exception as settle_exc:
|
|
1320
|
+
# See _handle_sync_exception: a dead channel means nothing can be
|
|
1321
|
+
# settled and the broker redelivers — warn, don't raise.
|
|
1322
|
+
if not _is_channel_gone(settle_exc):
|
|
1323
|
+
raise
|
|
1324
|
+
logger.warning(
|
|
1325
|
+
"Channel closed before settlement (%s); leaving message "
|
|
1326
|
+
"unsettled — the broker will redeliver it (at-least-once)",
|
|
1327
|
+
type(settle_exc).__name__,
|
|
1328
|
+
)
|
|
1329
|
+
|
|
1330
|
+
async def _handle_async_exception_inner(
|
|
1331
|
+
self,
|
|
1332
|
+
route: RouteDefinition,
|
|
1333
|
+
message: RabbitMessage,
|
|
1334
|
+
exc: Exception,
|
|
1335
|
+
) -> None:
|
|
1264
1336
|
if message.is_settled:
|
|
1265
1337
|
logger.warning("Exception after settlement: %s", exc)
|
|
1266
1338
|
return
|
|
@@ -146,6 +146,17 @@ class SyncBroker:
|
|
|
146
146
|
self._transport.on_blocked(value.on_blocked)
|
|
147
147
|
self._transport.on_unblocked(value.on_unblocked)
|
|
148
148
|
|
|
149
|
+
@property
|
|
150
|
+
def started(self) -> bool:
|
|
151
|
+
"""True between a successful ``start()`` and ``stop()``.
|
|
152
|
+
|
|
153
|
+
Public counterpart of ``_started`` — health checks
|
|
154
|
+
(:func:`rabbitkit.health.broker_health_check`) read this instead of
|
|
155
|
+
falling back to the private attribute (which emits a
|
|
156
|
+
DeprecationWarning).
|
|
157
|
+
"""
|
|
158
|
+
return self._started
|
|
159
|
+
|
|
149
160
|
@property
|
|
150
161
|
def config(self) -> RabbitConfig:
|
|
151
162
|
return self._config
|
|
@@ -930,7 +930,22 @@ class SyncTransport:
|
|
|
930
930
|
while self._consuming:
|
|
931
931
|
# process_data_events drains ALL channels' consumers + queued
|
|
932
932
|
# add_callback_threadsafe callbacks (acks from worker threads).
|
|
933
|
-
|
|
933
|
+
try:
|
|
934
|
+
self._connection.process_data_events(time_limit=1.0)
|
|
935
|
+
except ValueError as exc:
|
|
936
|
+
# pika's SelectConnection ioloop raises a bare
|
|
937
|
+
# ValueError("Timeout closed before call") when the
|
|
938
|
+
# connection died between poll ticks (e.g. broker restart).
|
|
939
|
+
# Re-raise it as the connection error it really is so
|
|
940
|
+
# SyncBroker.run()'s recovery loop reconnects instead of
|
|
941
|
+
# the consumer thread dying on an unrecognized exception.
|
|
942
|
+
if self._connection.is_closed or "Timeout closed before call" in str(exc):
|
|
943
|
+
import pika.exceptions
|
|
944
|
+
|
|
945
|
+
raise pika.exceptions.AMQPConnectionError(
|
|
946
|
+
f"connection lost mid-poll: {exc}"
|
|
947
|
+
) from exc
|
|
948
|
+
raise
|
|
934
949
|
# L14: process_data_events returning (rather than raising a
|
|
935
950
|
# connection error) is itself evidence the I/O loop is alive
|
|
936
951
|
# and pumping -- fire once per tick regardless of whether any
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rabbitkit
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Production-grade RabbitMQ toolkit — sync/async, decorator routing, retry, compression, full configurability
|
|
5
5
|
Author-email: Talaat Magdy <talaatmagdy75@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -60,6 +60,7 @@ Requires-Dist: rabbitkit[all]; extra == "dev"
|
|
|
60
60
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
61
61
|
Requires-Dist: httpx<1.0,>=0.27; extra == "dev"
|
|
62
62
|
Requires-Dist: prometheus-client<2.0,>=0.20; extra == "dev"
|
|
63
|
+
Requires-Dist: psutil>=5.9; extra == "dev"
|
|
63
64
|
Requires-Dist: mkdocs>=1.6.0; extra == "dev"
|
|
64
65
|
Requires-Dist: mkdocs-material>=9.5.0; extra == "dev"
|
|
65
66
|
Requires-Dist: mkdocstrings[python]>=0.26.0; extra == "dev"
|
|
@@ -76,7 +77,7 @@ Requires-Dist: testcontainers[rabbitmq]>=4.0.0; extra == "integration"
|
|
|
76
77
|
Requires-Dist: docker>=7.0.0; extra == "integration"
|
|
77
78
|
Dynamic: license-file
|
|
78
79
|
|
|
79
|
-
<p align="center"><img src="assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
80
|
+
<p align="center"><img src="https://raw.githubusercontent.com/talaatmagdyx/rabbitkit/main/assets/logo.svg" alt="rabbitkit" width="420"></p>
|
|
80
81
|
|
|
81
82
|
# rabbitkit
|
|
82
83
|
|
|
@@ -84,10 +85,10 @@ Dynamic: license-file
|
|
|
84
85
|
|
|
85
86
|
[](https://pypi.org/project/rabbitkit/)
|
|
86
87
|
[](https://github.com/talaatmagdyx/rabbitkit/actions/workflows/ci.yml)
|
|
87
|
-
[](pyproject.toml)
|
|
88
|
-
[](LICENSE)
|
|
89
|
-
[](pyproject.toml)
|
|
90
|
-
[](pyproject.toml)
|
|
88
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
89
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/LICENSE)
|
|
90
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
91
|
+
[](https://github.com/talaatmagdyx/rabbitkit/blob/main/pyproject.toml)
|
|
91
92
|
|
|
92
93
|
rabbitkit is a **RabbitMQ-first toolkit for Python services**. It gives you
|
|
93
94
|
clean decorators, safe retries, dead-letter queues, publisher confirms,
|
|
@@ -192,6 +193,8 @@ tests, production-ready lifecycle.
|
|
|
192
193
|
|
|
193
194
|
## Installation
|
|
194
195
|
|
|
196
|
+
Available on PyPI: **[pypi.org/project/rabbitkit](https://pypi.org/project/rabbitkit/)**
|
|
197
|
+
|
|
195
198
|
```bash
|
|
196
199
|
pip install rabbitkit[async] # AsyncBroker (aio-pika)
|
|
197
200
|
pip install rabbitkit[sync] # SyncBroker (pika)
|
|
@@ -401,13 +404,13 @@ only when loss is acceptable.
|
|
|
401
404
|
## Production profile
|
|
402
405
|
|
|
403
406
|
The recommended baseline (see the
|
|
404
|
-
[production checklist](docs/production/checklist.md)): quorum queues (+
|
|
407
|
+
[production checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)): quorum queues (+
|
|
405
408
|
`delivery_limit`), per-queue retry/DLQ topology, publisher confirms on,
|
|
406
409
|
mandatory publishing where routing matters, checked `PublishOutcome`s,
|
|
407
410
|
explicit ack policies, structured logs, split readiness/liveness probes,
|
|
408
411
|
management-API metrics for queue depth and consumer lag, idempotent
|
|
409
412
|
handlers. Migrating existing classic queues to quorum? There's a tool:
|
|
410
|
-
`rabbitkit topology migrate` ([guide](docs/quorum-migration.md)).
|
|
413
|
+
`rabbitkit topology migrate` ([guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/quorum-migration.md)).
|
|
411
414
|
|
|
412
415
|
## Observability
|
|
413
416
|
|
|
@@ -430,7 +433,7 @@ middleware (bring any `CircuitBreakerProtocol` implementation, e.g.
|
|
|
430
433
|
pybreaker).
|
|
431
434
|
|
|
432
435
|
**Experimental** (may change without a deprecation cycle — read the
|
|
433
|
-
[stability policy](docs/stability-policy.md)): RPC over direct reply-to,
|
|
436
|
+
[stability policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)): RPC over direct reply-to,
|
|
434
437
|
distributed locking, message signing, result backends, stream queues, the
|
|
435
438
|
monitoring dashboard. Notable caveats: the default signing nonce cache is
|
|
436
439
|
per-process (use a shared cache for real replay protection), and never
|
|
@@ -494,7 +497,7 @@ rabbitkit health myapp.main:broker
|
|
|
494
497
|
The DLQ replay acks a message only after its republish is broker-confirmed —
|
|
495
498
|
the recovery tool cannot itself lose messages.
|
|
496
499
|
|
|
497
|
-
<p align="center"><img src="assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
500
|
+
<p align="center"><img src="https://raw.githubusercontent.com/talaatmagdyx/rabbitkit/main/assets/demo.svg" alt="rabbitkit dlq inspect and replay demo" width="720"></p>
|
|
498
501
|
|
|
499
502
|
## Where rabbitkit fits
|
|
500
503
|
|
|
@@ -520,7 +523,7 @@ test infrastructure in every service.
|
|
|
520
523
|
- at-most-once behavior is acceptable and a raw client is enough
|
|
521
524
|
|
|
522
525
|
A detailed framework-by-framework comparison lives in
|
|
523
|
-
[docs/comparison.md](docs/comparison.md).
|
|
526
|
+
[docs/comparison.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/comparison.md).
|
|
524
527
|
|
|
525
528
|
## Architecture
|
|
526
529
|
|
|
@@ -551,25 +554,25 @@ Python ≥ 3.11 (tested: 3.11 / 3.12 / 3.13 / 3.14; 3.15 pre-release experimenta
|
|
|
551
554
|
|
|
552
555
|
**📚 Full rendered docs: [talaatmagdyx.github.io/rabbitkit](https://talaatmagdyx.github.io/rabbitkit/)**
|
|
553
556
|
|
|
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)
|
|
557
|
+
- [Getting Started](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/getting-started.md)
|
|
558
|
+
- [Full Guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/full-guide.md)
|
|
559
|
+
- [Message Safety](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/message-safety.md)
|
|
560
|
+
- [Retry & DLQ](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/retry-and-dlq.md)
|
|
561
|
+
- [Production Checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)
|
|
562
|
+
- [Idempotency Contract](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/idempotency.md)
|
|
563
|
+
- [Kubernetes](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/kubernetes.md)
|
|
564
|
+
- [Quorum Migration](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/quorum-migration.md)
|
|
565
|
+
- [Security](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/security.md)
|
|
566
|
+
- [Stability Policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)
|
|
567
|
+
- [Troubleshooting](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/troubleshooting.md)
|
|
565
568
|
|
|
566
569
|
## Contributing & security
|
|
567
570
|
|
|
568
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development and quality
|
|
571
|
+
See [CONTRIBUTING.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/CONTRIBUTING.md) for local development and quality
|
|
569
572
|
gates (ruff, `mypy --strict`, near-total test coverage — the bar is real).
|
|
570
|
-
Found a vulnerability? Follow [SECURITY.md](SECURITY.md) and report it
|
|
573
|
+
Found a vulnerability? Follow [SECURITY.md](https://github.com/talaatmagdyx/rabbitkit/blob/main/SECURITY.md) and report it
|
|
571
574
|
privately.
|
|
572
575
|
|
|
573
576
|
## License
|
|
574
577
|
|
|
575
|
-
[MIT](LICENSE)
|
|
578
|
+
[MIT](https://github.com/talaatmagdyx/rabbitkit/blob/main/LICENSE)
|
|
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
|
|
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
|
|
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
|
|
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
|