rabbitkit 0.19.0__tar.gz → 0.20.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rabbitkit-0.19.0/src/rabbitkit.egg-info → rabbitkit-0.20.0}/PKG-INFO +48 -18
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/README.md +47 -17
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/pyproject.toml +5 -2
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/__init__.py +50 -1
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/_version.py +1 -1
- rabbitkit-0.20.0/src/rabbitkit/async_/batch.py +431 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/async_/broker.py +646 -98
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/async_/connection.py +277 -26
- rabbitkit-0.20.0/src/rabbitkit/async_/consumers.py +300 -0
- rabbitkit-0.20.0/src/rabbitkit/async_/pool.py +1110 -0
- rabbitkit-0.20.0/src/rabbitkit/async_/reconnect.py +340 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/async_/transport.py +849 -293
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/dlq.py +101 -11
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/concurrency.py +90 -30
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/app.py +46 -16
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/bulk.py +39 -7
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/bulk_runner.py +63 -32
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/config.py +465 -55
- rabbitkit-0.20.0/src/rabbitkit/core/dead_letter.py +242 -0
- rabbitkit-0.20.0/src/rabbitkit/core/errors.py +518 -0
- rabbitkit-0.20.0/src/rabbitkit/core/failure_copy.py +229 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/logging.py +69 -10
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/message.py +108 -31
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/pipeline.py +501 -67
- rabbitkit-0.20.0/src/rabbitkit/core/profiles.py +720 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/protocols.py +6 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/registry.py +54 -6
- rabbitkit-0.20.0/src/rabbitkit/core/reserved_headers.py +95 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/retry_handoff.py +18 -1
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/route.py +78 -1
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/router.py +8 -3
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/settlement.py +66 -16
- rabbitkit-0.20.0/src/rabbitkit/core/signals.py +49 -0
- rabbitkit-0.20.0/src/rabbitkit/core/streams.py +580 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/topology.py +192 -1
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/topology_dispatch.py +31 -3
- rabbitkit-0.20.0/src/rabbitkit/core/topology_validation.py +401 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/types.py +237 -4
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/dlq.py +396 -20
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/experimental/__init__.py +27 -5
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/fastapi.py +10 -3
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/health.py +31 -9
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/highload/async_acker.py +37 -14
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/highload/batch.py +44 -12
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/management.py +5 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/__init__.py +11 -1
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/deduplication.py +161 -48
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/exception.py +24 -5
- rabbitkit-0.20.0/src/rabbitkit/middleware/metrics.py +1061 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/otel.py +62 -4
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/retry.py +469 -187
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/signing.py +9 -3
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/timeout.py +35 -18
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/queue_metrics.py +26 -4
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/rpc.py +26 -3
- rabbitkit-0.20.0/src/rabbitkit/streams.py +46 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/sync/broker.py +348 -48
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/sync/connection.py +29 -20
- rabbitkit-0.20.0/src/rabbitkit/sync/consumers.py +193 -0
- rabbitkit-0.20.0/src/rabbitkit/sync/deadlines.py +223 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/sync/pool.py +111 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/sync/transport.py +639 -148
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/testing/__init__.py +2 -2
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/testing/broker.py +31 -8
- {rabbitkit-0.19.0 → rabbitkit-0.20.0/src/rabbitkit.egg-info}/PKG-INFO +48 -18
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit.egg-info/SOURCES.txt +10 -0
- rabbitkit-0.19.0/src/rabbitkit/async_/batch.py +0 -242
- rabbitkit-0.19.0/src/rabbitkit/async_/pool.py +0 -472
- rabbitkit-0.19.0/src/rabbitkit/core/errors.py +0 -266
- rabbitkit-0.19.0/src/rabbitkit/core/profiles.py +0 -483
- rabbitkit-0.19.0/src/rabbitkit/middleware/metrics.py +0 -501
- rabbitkit-0.19.0/src/rabbitkit/streams.py +0 -139
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/LICENSE +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/setup.cfg +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/aio/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/async_/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/asyncapi/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/asyncapi/generator.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/asyncapi/schema.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/_utils.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/health.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/migrate.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/routes.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/run.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/shell.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/cli/commands/topology.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/env_config.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/path.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/quorum.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/core/sanitizer.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/dashboard/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/dashboard/app.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/di/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/di/context.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/di/depends.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/di/resolver.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/highload/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/highload/backpressure.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/locking.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/base.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/circuit_breaker.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/compression.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/error_classifier.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/middleware/rate_limit.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/py.typed +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/results/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/results/backend.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/results/middleware.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/serialization/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/serialization/base.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/serialization/json.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/serialization/msgspec.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/serialization/pipeline.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/sync/__init__.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/sync/batch.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/testing/app.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit/testing/fixtures.py +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit.egg-info/dependency_links.txt +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit.egg-info/entry_points.txt +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/src/rabbitkit.egg-info/requires.txt +0 -0
- {rabbitkit-0.19.0 → rabbitkit-0.20.0}/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.
|
|
3
|
+
Version: 0.20.0
|
|
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
|
|
@@ -258,9 +258,12 @@ async def publish_order() -> None:
|
|
|
258
258
|
outcome.raise_for_status()
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
A publish can be `CONFIRMED`, `SENT`, `RETURNED`, `NACKED`, `
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
A publish can be `CONFIRMED`, `SENT`, `RETURNED`, `NACKED`, `NOT_SENT`
|
|
262
|
+
(definitely never left the client: safe to publish again), `UNKNOWN` or
|
|
263
|
+
`TIMEOUT` (may have reached the broker: publish again only with the same
|
|
264
|
+
`message_id`), or `ERROR`. `outcome.reason` says why. Application code can
|
|
265
|
+
treat those as different states instead of assuming "publish called" means
|
|
266
|
+
"message safe".
|
|
264
267
|
|
|
265
268
|
### 3. Add retry and DLQ handling
|
|
266
269
|
|
|
@@ -291,7 +294,11 @@ orders.created
|
|
|
291
294
|
Transient failures retry with backoff. Permanent failures skip the ladder
|
|
292
295
|
and go straight to the DLQ. By default, rejected messages do not disappear
|
|
293
296
|
silently — every rejecting route gets a DLQ unless you explicitly opt into
|
|
294
|
-
discard behavior.
|
|
297
|
+
discard behavior. A terminal failure reaches the DLQ as a broker-confirmed
|
|
298
|
+
copy carrying the final attempt's error class, sanitized message, failure
|
|
299
|
+
category, attempt count, timestamps and original queue/exchange/routing key;
|
|
300
|
+
`RetryConfig.max_retry_age` bounds the total retry time and
|
|
301
|
+
`RetryConfig.exponential(...)` builds the delay ladder.
|
|
295
302
|
|
|
296
303
|
### 4. Test it without RabbitMQ
|
|
297
304
|
|
|
@@ -420,8 +427,11 @@ def commit_batch(successful: list[RabbitMessage], failed: list[RabbitMessage]) -
|
|
|
420
427
|
|
|
421
428
|
Opt-in **reliability profiles** (`critical_config()`, `broker.preflight("critical",
|
|
422
429
|
management_client=...)`) verify confirms/mandatory/persistence, a 256 KiB body
|
|
423
|
-
cap,
|
|
424
|
-
|
|
430
|
+
cap, bounded in-flight publish bytes, quorum queues with at-least-once
|
|
431
|
+
dead-lettering, a quorum retry chain with a metadata-stamped DLQ copy, a
|
|
432
|
+
required processing deadline and no `ACK_FIRST` — and report anything they
|
|
433
|
+
cannot verify as *unverified*, never green
|
|
434
|
+
([reliable messaging contract](docs/reliable-messaging.md)). Retry/DLQ triage headers are sanitized by
|
|
425
435
|
default, and a failing delay-queue handoff backs off instead of hot-looping.
|
|
426
436
|
`CoalescingAcker` (and `AsyncCoalescingAcker`) is the only place a cumulative
|
|
427
437
|
`basic.ack(multiple=True)` may originate: it knows every delivery on the
|
|
@@ -475,9 +485,13 @@ duplicates). The rule is simple:
|
|
|
475
485
|
| Handler raises forever | Retry ladder, then DLQ |
|
|
476
486
|
| Malformed payload | Classified permanent, preserved in DLQ |
|
|
477
487
|
| Reject with no DLX | Safe default auto-provisions a DLQ — no silent discard |
|
|
478
|
-
| Retry publish times out | Original is **not** acked as if the retry succeeded |
|
|
488
|
+
| Retry publish times out | Original is **not** acked as if the retry succeeded; the delivery is held for a capped backoff before it is requeued, so it cannot hot-loop |
|
|
489
|
+
| Handler raises `AckMessage`/`RejectMessage` | Settled exactly as asked — never retried |
|
|
490
|
+
| Result publish fails after dedup | The redelivery is processed again, not acked as a duplicate |
|
|
491
|
+
| Infrastructure-managed topology | `TopologyMode.PASSIVE_ONLY` performs no writes; `broker.verify_topology(management_client)` detects type/argument/binding drift |
|
|
479
492
|
| DLQ replay publish fails | DLQ message is **not** removed as if replay succeeded |
|
|
480
493
|
| Message unroutable | Mandatory publishing returns a distinct `RETURNED` outcome |
|
|
494
|
+
| Oversized delivery (`ConsumerConfig.max_body_bytes`) | Dead-lettered as `invalid_message` before any middleware or the handler runs |
|
|
481
495
|
| Broker blips | Readiness changes; liveness does **not** kill the pod |
|
|
482
496
|
| Pod gets SIGTERM | Consumers stop first, in-flight work drains |
|
|
483
497
|
| CI has no RabbitMQ | `TestBroker` runs the real pipeline in memory |
|
|
@@ -489,8 +503,8 @@ Settlement is a decision, not a side effect hidden in a callback.
|
|
|
489
503
|
|
|
490
504
|
| Policy | Behavior | Use case |
|
|
491
505
|
|---|---|---|
|
|
492
|
-
| `AUTO` | Ack on success, retry/reject on failure | Most consumers |
|
|
493
|
-
| `MANUAL` | Handler owns ack/nack/reject | Custom settlement flows |
|
|
506
|
+
| `AUTO` | Ack on success, retry/reject on failure (without retry: a transient failure is requeued once, then dead-lettered) | Most consumers |
|
|
507
|
+
| `MANUAL` | Handler owns ack/nack/reject; if it raises without settling, the `AUTO` failure settlement applies | Custom settlement flows |
|
|
494
508
|
| `NACK_ON_ERROR` | Ack on success, nack on failure | Never silently accept failed work |
|
|
495
509
|
| `ACK_FIRST` | Ack before the handler runs | At-most-once workloads |
|
|
496
510
|
|
|
@@ -511,14 +525,22 @@ handlers. Migrating existing classic queues to quorum? There's a tool:
|
|
|
511
525
|
## Observability
|
|
512
526
|
|
|
513
527
|
Structured logs carry message context (`message_id`, `correlation_id`,
|
|
514
|
-
routing, queue, handler, retry count, settlement, duration, error type)
|
|
515
|
-
|
|
528
|
+
routing, queue, handler, retry count, settlement, duration, error type) and
|
|
529
|
+
redact secrets once logging is configured
|
|
530
|
+
(`RabbitConfig(logging=LoggingConfig())`; redaction is opt-in). Metrics cover consumed/acked/nacked/
|
|
516
531
|
retried/dead-lettered counts, publish outcomes, handler latency,
|
|
517
532
|
redeliveries, reconnects, confirm latency, in-flight handlers, broker/consumer
|
|
518
533
|
lifecycle gauges, bulk-publish and settlement outcomes, retry-handoff
|
|
519
|
-
failures,
|
|
534
|
+
failures, resource alarms by cause (disk or memory, from `connection.blocked`), and — via the management API
|
|
535
|
+
poller — queue depth, consumer lag and quorum-queue member health. Tracing is standard OpenTelemetry
|
|
520
536
|
(`pip install rabbitkit[otel]`): W3C context propagation over AMQP headers,
|
|
521
|
-
one continuous trace from publish to consume.
|
|
537
|
+
one continuous trace from publish to consume. Prometheus alert rules with
|
|
538
|
+
`promtool` tests ship in `deploy/prometheus/rabbitkit-alerts.yaml`, each linked
|
|
539
|
+
to a runbook in `docs/runbooks/` and each threshold explained; they need only
|
|
540
|
+
rabbitkit's own metrics (the broker's `rabbitmq_prometheus` plugin is an
|
|
541
|
+
optional alternative group). For the full reliability contract (publish
|
|
542
|
+
outcomes, settlement, failure taxonomy, durability scope) see
|
|
543
|
+
`docs/reliable-messaging.md`.
|
|
522
544
|
|
|
523
545
|
## Advanced & experimental
|
|
524
546
|
|
|
@@ -528,12 +550,16 @@ with per-item outcomes, safe ack coalescing (`CoalescingAcker`), reliability
|
|
|
528
550
|
profiles + `preflight`, pipelined sync confirms (`SyncBatchPublisher`), DLQ inspector + replay CLI, management API client,
|
|
529
551
|
topology validation/drift/migration CLI, health watcher, circuit-breaker
|
|
530
552
|
middleware (bring any `CircuitBreakerProtocol` implementation, e.g.
|
|
531
|
-
pybreaker)
|
|
553
|
+
pybreaker), and RabbitMQ streams: `subscriber(stream_offset="first")` on a
|
|
554
|
+
`QueueType.STREAM` queue, per-delivery `message.stream_offset`, retention
|
|
555
|
+
options, and an `OffsetStore` protocol (in-memory by default; plug in your
|
|
556
|
+
own storage) so restarts and reconnects resume where they left off (at-least-once; no retry/DLQ on streams). See
|
|
557
|
+
[Streams](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/streams.md).
|
|
532
558
|
|
|
533
559
|
**Experimental** (may change without a deprecation cycle — read the
|
|
534
560
|
[stability policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)): RPC over direct reply-to,
|
|
535
|
-
distributed locking, message signing, result backends,
|
|
536
|
-
|
|
561
|
+
distributed locking, message signing, result backends, the monitoring
|
|
562
|
+
dashboard. Notable caveats: the default signing nonce cache is
|
|
537
563
|
per-process (use a shared cache for real replay protection), and never
|
|
538
564
|
expose the dashboard publicly without authentication.
|
|
539
565
|
|
|
@@ -594,7 +620,9 @@ rabbitkit health myapp.main:broker
|
|
|
594
620
|
```
|
|
595
621
|
|
|
596
622
|
The DLQ replay acks a message only after its republish is broker-confirmed —
|
|
597
|
-
the recovery tool cannot itself lose messages.
|
|
623
|
+
the recovery tool cannot itself lose messages. Every replay is audit-logged
|
|
624
|
+
and stamps `x-rabbitkit-replay-count`; a message replayed 3 times is refused
|
|
625
|
+
unless you pass `--ignore-replay-limit`.
|
|
598
626
|
|
|
599
627
|
<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>
|
|
600
628
|
|
|
@@ -735,6 +763,8 @@ Python ≥ 3.11 (tested: 3.11 / 3.12 / 3.13 / 3.14; 3.15 pre-release experimenta
|
|
|
735
763
|
- [Full Guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/full-guide.md)
|
|
736
764
|
- [Message Safety](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/message-safety.md)
|
|
737
765
|
- [Retry & DLQ](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/retry-and-dlq.md)
|
|
766
|
+
- [Reliable Messaging — contract & CRITICAL profile](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/reliable-messaging.md)
|
|
767
|
+
- [Streams — offsets, replay, retention](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/streams.md)
|
|
738
768
|
- [Production Patterns — reference code](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/patterns.md)
|
|
739
769
|
- [Scale & Reliability Handbook — millions of messages](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/scale.md)
|
|
740
770
|
- [Production Checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)
|
|
@@ -177,9 +177,12 @@ async def publish_order() -> None:
|
|
|
177
177
|
outcome.raise_for_status()
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
A publish can be `CONFIRMED`, `SENT`, `RETURNED`, `NACKED`, `
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
A publish can be `CONFIRMED`, `SENT`, `RETURNED`, `NACKED`, `NOT_SENT`
|
|
181
|
+
(definitely never left the client: safe to publish again), `UNKNOWN` or
|
|
182
|
+
`TIMEOUT` (may have reached the broker: publish again only with the same
|
|
183
|
+
`message_id`), or `ERROR`. `outcome.reason` says why. Application code can
|
|
184
|
+
treat those as different states instead of assuming "publish called" means
|
|
185
|
+
"message safe".
|
|
183
186
|
|
|
184
187
|
### 3. Add retry and DLQ handling
|
|
185
188
|
|
|
@@ -210,7 +213,11 @@ orders.created
|
|
|
210
213
|
Transient failures retry with backoff. Permanent failures skip the ladder
|
|
211
214
|
and go straight to the DLQ. By default, rejected messages do not disappear
|
|
212
215
|
silently — every rejecting route gets a DLQ unless you explicitly opt into
|
|
213
|
-
discard behavior.
|
|
216
|
+
discard behavior. A terminal failure reaches the DLQ as a broker-confirmed
|
|
217
|
+
copy carrying the final attempt's error class, sanitized message, failure
|
|
218
|
+
category, attempt count, timestamps and original queue/exchange/routing key;
|
|
219
|
+
`RetryConfig.max_retry_age` bounds the total retry time and
|
|
220
|
+
`RetryConfig.exponential(...)` builds the delay ladder.
|
|
214
221
|
|
|
215
222
|
### 4. Test it without RabbitMQ
|
|
216
223
|
|
|
@@ -339,8 +346,11 @@ def commit_batch(successful: list[RabbitMessage], failed: list[RabbitMessage]) -
|
|
|
339
346
|
|
|
340
347
|
Opt-in **reliability profiles** (`critical_config()`, `broker.preflight("critical",
|
|
341
348
|
management_client=...)`) verify confirms/mandatory/persistence, a 256 KiB body
|
|
342
|
-
cap,
|
|
343
|
-
|
|
349
|
+
cap, bounded in-flight publish bytes, quorum queues with at-least-once
|
|
350
|
+
dead-lettering, a quorum retry chain with a metadata-stamped DLQ copy, a
|
|
351
|
+
required processing deadline and no `ACK_FIRST` — and report anything they
|
|
352
|
+
cannot verify as *unverified*, never green
|
|
353
|
+
([reliable messaging contract](docs/reliable-messaging.md)). Retry/DLQ triage headers are sanitized by
|
|
344
354
|
default, and a failing delay-queue handoff backs off instead of hot-looping.
|
|
345
355
|
`CoalescingAcker` (and `AsyncCoalescingAcker`) is the only place a cumulative
|
|
346
356
|
`basic.ack(multiple=True)` may originate: it knows every delivery on the
|
|
@@ -394,9 +404,13 @@ duplicates). The rule is simple:
|
|
|
394
404
|
| Handler raises forever | Retry ladder, then DLQ |
|
|
395
405
|
| Malformed payload | Classified permanent, preserved in DLQ |
|
|
396
406
|
| Reject with no DLX | Safe default auto-provisions a DLQ — no silent discard |
|
|
397
|
-
| Retry publish times out | Original is **not** acked as if the retry succeeded |
|
|
407
|
+
| Retry publish times out | Original is **not** acked as if the retry succeeded; the delivery is held for a capped backoff before it is requeued, so it cannot hot-loop |
|
|
408
|
+
| Handler raises `AckMessage`/`RejectMessage` | Settled exactly as asked — never retried |
|
|
409
|
+
| Result publish fails after dedup | The redelivery is processed again, not acked as a duplicate |
|
|
410
|
+
| Infrastructure-managed topology | `TopologyMode.PASSIVE_ONLY` performs no writes; `broker.verify_topology(management_client)` detects type/argument/binding drift |
|
|
398
411
|
| DLQ replay publish fails | DLQ message is **not** removed as if replay succeeded |
|
|
399
412
|
| Message unroutable | Mandatory publishing returns a distinct `RETURNED` outcome |
|
|
413
|
+
| Oversized delivery (`ConsumerConfig.max_body_bytes`) | Dead-lettered as `invalid_message` before any middleware or the handler runs |
|
|
400
414
|
| Broker blips | Readiness changes; liveness does **not** kill the pod |
|
|
401
415
|
| Pod gets SIGTERM | Consumers stop first, in-flight work drains |
|
|
402
416
|
| CI has no RabbitMQ | `TestBroker` runs the real pipeline in memory |
|
|
@@ -408,8 +422,8 @@ Settlement is a decision, not a side effect hidden in a callback.
|
|
|
408
422
|
|
|
409
423
|
| Policy | Behavior | Use case |
|
|
410
424
|
|---|---|---|
|
|
411
|
-
| `AUTO` | Ack on success, retry/reject on failure | Most consumers |
|
|
412
|
-
| `MANUAL` | Handler owns ack/nack/reject | Custom settlement flows |
|
|
425
|
+
| `AUTO` | Ack on success, retry/reject on failure (without retry: a transient failure is requeued once, then dead-lettered) | Most consumers |
|
|
426
|
+
| `MANUAL` | Handler owns ack/nack/reject; if it raises without settling, the `AUTO` failure settlement applies | Custom settlement flows |
|
|
413
427
|
| `NACK_ON_ERROR` | Ack on success, nack on failure | Never silently accept failed work |
|
|
414
428
|
| `ACK_FIRST` | Ack before the handler runs | At-most-once workloads |
|
|
415
429
|
|
|
@@ -430,14 +444,22 @@ handlers. Migrating existing classic queues to quorum? There's a tool:
|
|
|
430
444
|
## Observability
|
|
431
445
|
|
|
432
446
|
Structured logs carry message context (`message_id`, `correlation_id`,
|
|
433
|
-
routing, queue, handler, retry count, settlement, duration, error type)
|
|
434
|
-
|
|
447
|
+
routing, queue, handler, retry count, settlement, duration, error type) and
|
|
448
|
+
redact secrets once logging is configured
|
|
449
|
+
(`RabbitConfig(logging=LoggingConfig())`; redaction is opt-in). Metrics cover consumed/acked/nacked/
|
|
435
450
|
retried/dead-lettered counts, publish outcomes, handler latency,
|
|
436
451
|
redeliveries, reconnects, confirm latency, in-flight handlers, broker/consumer
|
|
437
452
|
lifecycle gauges, bulk-publish and settlement outcomes, retry-handoff
|
|
438
|
-
failures,
|
|
453
|
+
failures, resource alarms by cause (disk or memory, from `connection.blocked`), and — via the management API
|
|
454
|
+
poller — queue depth, consumer lag and quorum-queue member health. Tracing is standard OpenTelemetry
|
|
439
455
|
(`pip install rabbitkit[otel]`): W3C context propagation over AMQP headers,
|
|
440
|
-
one continuous trace from publish to consume.
|
|
456
|
+
one continuous trace from publish to consume. Prometheus alert rules with
|
|
457
|
+
`promtool` tests ship in `deploy/prometheus/rabbitkit-alerts.yaml`, each linked
|
|
458
|
+
to a runbook in `docs/runbooks/` and each threshold explained; they need only
|
|
459
|
+
rabbitkit's own metrics (the broker's `rabbitmq_prometheus` plugin is an
|
|
460
|
+
optional alternative group). For the full reliability contract (publish
|
|
461
|
+
outcomes, settlement, failure taxonomy, durability scope) see
|
|
462
|
+
`docs/reliable-messaging.md`.
|
|
441
463
|
|
|
442
464
|
## Advanced & experimental
|
|
443
465
|
|
|
@@ -447,12 +469,16 @@ with per-item outcomes, safe ack coalescing (`CoalescingAcker`), reliability
|
|
|
447
469
|
profiles + `preflight`, pipelined sync confirms (`SyncBatchPublisher`), DLQ inspector + replay CLI, management API client,
|
|
448
470
|
topology validation/drift/migration CLI, health watcher, circuit-breaker
|
|
449
471
|
middleware (bring any `CircuitBreakerProtocol` implementation, e.g.
|
|
450
|
-
pybreaker)
|
|
472
|
+
pybreaker), and RabbitMQ streams: `subscriber(stream_offset="first")` on a
|
|
473
|
+
`QueueType.STREAM` queue, per-delivery `message.stream_offset`, retention
|
|
474
|
+
options, and an `OffsetStore` protocol (in-memory by default; plug in your
|
|
475
|
+
own storage) so restarts and reconnects resume where they left off (at-least-once; no retry/DLQ on streams). See
|
|
476
|
+
[Streams](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/streams.md).
|
|
451
477
|
|
|
452
478
|
**Experimental** (may change without a deprecation cycle — read the
|
|
453
479
|
[stability policy](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/stability-policy.md)): RPC over direct reply-to,
|
|
454
|
-
distributed locking, message signing, result backends,
|
|
455
|
-
|
|
480
|
+
distributed locking, message signing, result backends, the monitoring
|
|
481
|
+
dashboard. Notable caveats: the default signing nonce cache is
|
|
456
482
|
per-process (use a shared cache for real replay protection), and never
|
|
457
483
|
expose the dashboard publicly without authentication.
|
|
458
484
|
|
|
@@ -513,7 +539,9 @@ rabbitkit health myapp.main:broker
|
|
|
513
539
|
```
|
|
514
540
|
|
|
515
541
|
The DLQ replay acks a message only after its republish is broker-confirmed —
|
|
516
|
-
the recovery tool cannot itself lose messages.
|
|
542
|
+
the recovery tool cannot itself lose messages. Every replay is audit-logged
|
|
543
|
+
and stamps `x-rabbitkit-replay-count`; a message replayed 3 times is refused
|
|
544
|
+
unless you pass `--ignore-replay-limit`.
|
|
517
545
|
|
|
518
546
|
<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>
|
|
519
547
|
|
|
@@ -654,6 +682,8 @@ Python ≥ 3.11 (tested: 3.11 / 3.12 / 3.13 / 3.14; 3.15 pre-release experimenta
|
|
|
654
682
|
- [Full Guide](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/guide/full-guide.md)
|
|
655
683
|
- [Message Safety](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/message-safety.md)
|
|
656
684
|
- [Retry & DLQ](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/retry-and-dlq.md)
|
|
685
|
+
- [Reliable Messaging — contract & CRITICAL profile](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/reliable-messaging.md)
|
|
686
|
+
- [Streams — offsets, replay, retention](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/streams.md)
|
|
657
687
|
- [Production Patterns — reference code](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/patterns.md)
|
|
658
688
|
- [Scale & Reliability Handbook — millions of messages](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/scale.md)
|
|
659
689
|
- [Production Checklist](https://github.com/talaatmagdyx/rabbitkit/blob/main/docs/production/checklist.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rabbitkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.20.0"
|
|
4
4
|
description = "Production-grade RabbitMQ toolkit — sync/async, decorator routing, retry, compression, full configurability"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -153,7 +153,10 @@ warn_unused_configs = true
|
|
|
153
153
|
[tool.pytest.ini_options]
|
|
154
154
|
asyncio_mode = "auto"
|
|
155
155
|
testpaths = ["tests"]
|
|
156
|
-
markers = [
|
|
156
|
+
markers = [
|
|
157
|
+
"integration: integration tests (may need RabbitMQ)",
|
|
158
|
+
"cluster_chaos: slow multi-container failure tests (3-node cluster, toxiproxy); nightly only, not part of -m integration",
|
|
159
|
+
]
|
|
157
160
|
filterwarnings = [
|
|
158
161
|
# rabbitkit's own operator-guidance warnings, exercised deliberately by
|
|
159
162
|
# tests (dedicated pytest.warns tests still assert they fire — pytest.warns
|
|
@@ -38,16 +38,27 @@ from rabbitkit.core.config import (
|
|
|
38
38
|
WorkerConfig,
|
|
39
39
|
)
|
|
40
40
|
from rabbitkit.core.errors import (
|
|
41
|
+
AuthenticationFailedError,
|
|
41
42
|
BackpressureError,
|
|
42
43
|
BrokerNotStartedError,
|
|
43
44
|
ConfigurationError,
|
|
44
45
|
ConfigValidationError,
|
|
46
|
+
DLQOperationDeniedError,
|
|
47
|
+
HandlerTimeoutError,
|
|
48
|
+
InboundMessageTooLargeError,
|
|
49
|
+
InfrastructureError,
|
|
50
|
+
InvalidMessageError,
|
|
45
51
|
MessageTooLargeError,
|
|
46
52
|
MissingDependencyError,
|
|
53
|
+
PermanentConnectionError,
|
|
54
|
+
ReconnectBudgetExhaustedError,
|
|
47
55
|
SettlementError,
|
|
56
|
+
TerminalConnectionError,
|
|
57
|
+
TLSVerificationError,
|
|
48
58
|
TopologyValidationError,
|
|
49
59
|
UnsafeToBrowseError,
|
|
50
60
|
UnsafeTopologyError,
|
|
61
|
+
VhostAccessRefusedError,
|
|
51
62
|
)
|
|
52
63
|
from rabbitkit.core.logging import DEFAULT_REDACT_KEYS, LoggingConfig, configure_structlog
|
|
53
64
|
from rabbitkit.core.message import AckMessage, NackMessage, RabbitMessage, RejectMessage
|
|
@@ -79,30 +90,36 @@ from rabbitkit.core.settlement import (
|
|
|
79
90
|
UnknownDeliveryError,
|
|
80
91
|
)
|
|
81
92
|
from rabbitkit.core.topology import RabbitExchange, RabbitQueue
|
|
93
|
+
from rabbitkit.core.topology_validation import TopologyFinding, TopologyReport, verify_topology
|
|
82
94
|
from rabbitkit.core.types import (
|
|
83
95
|
AckPolicy,
|
|
84
96
|
BulkPublishStatus,
|
|
85
97
|
ClassifiedError,
|
|
98
|
+
DeadLetterStrategy,
|
|
86
99
|
DeduplicationMarkPolicy,
|
|
87
100
|
DeliveryState,
|
|
101
|
+
ErrorCategory,
|
|
88
102
|
ErrorSeverity,
|
|
89
103
|
ExchangeType,
|
|
104
|
+
FailureCategory,
|
|
90
105
|
FlushReason,
|
|
91
106
|
HandoffState,
|
|
92
107
|
MessageEnvelope,
|
|
93
108
|
PreflightStatus,
|
|
94
109
|
PublishOutcome,
|
|
110
|
+
PublishReason,
|
|
95
111
|
PublishStatus,
|
|
96
112
|
QueueType,
|
|
97
113
|
RejectWithoutDLXPolicy,
|
|
98
114
|
ReliabilityProfile,
|
|
99
115
|
SettlementAction,
|
|
100
116
|
SettlementItemStatus,
|
|
117
|
+
TerminalAction,
|
|
101
118
|
TopologyMode,
|
|
102
119
|
)
|
|
103
120
|
from rabbitkit.di import Context, ContextRepo, Depends, DIResolver, Header, Path
|
|
104
121
|
from rabbitkit.di.resolver import DependencyScope
|
|
105
|
-
from rabbitkit.dlq import DLQInspector, ReplayResult
|
|
122
|
+
from rabbitkit.dlq import DLQInspector, ReplayRequest, ReplayResult
|
|
106
123
|
from rabbitkit.fastapi import rabbitkit_lifespan
|
|
107
124
|
from rabbitkit.health import (
|
|
108
125
|
AsyncHealthWatcher,
|
|
@@ -153,6 +170,13 @@ from rabbitkit.serialization.pipeline import (
|
|
|
153
170
|
RawDecoder,
|
|
154
171
|
SerializationPipeline,
|
|
155
172
|
)
|
|
173
|
+
from rabbitkit.streams import (
|
|
174
|
+
InMemoryOffsetStore,
|
|
175
|
+
OffsetStore,
|
|
176
|
+
StreamConsumerConfig,
|
|
177
|
+
StreamOffset,
|
|
178
|
+
StreamOffsetType,
|
|
179
|
+
)
|
|
156
180
|
from rabbitkit.sync.batch import SyncBatchPublisher
|
|
157
181
|
from rabbitkit.sync.broker import SyncBroker
|
|
158
182
|
|
|
@@ -168,6 +192,7 @@ __all__ = [
|
|
|
168
192
|
"AsyncCoalescingAcker",
|
|
169
193
|
"AsyncHealthWatcher",
|
|
170
194
|
"AsyncWorkerPool",
|
|
195
|
+
"AuthenticationFailedError",
|
|
171
196
|
"BackpressureConfig",
|
|
172
197
|
"BackpressureError",
|
|
173
198
|
"BatchAckConfig",
|
|
@@ -201,26 +226,35 @@ __all__ = [
|
|
|
201
226
|
"CoordinatorError",
|
|
202
227
|
"DIResolver",
|
|
203
228
|
"DLQInspector",
|
|
229
|
+
"DLQOperationDeniedError",
|
|
204
230
|
"DataclassDecoder",
|
|
231
|
+
"DeadLetterStrategy",
|
|
205
232
|
"DeduplicationConfig",
|
|
206
233
|
"DeduplicationMarkPolicy",
|
|
207
234
|
"DeduplicationMiddleware",
|
|
208
235
|
"DeliveryState",
|
|
209
236
|
"DependencyScope",
|
|
210
237
|
"Depends",
|
|
238
|
+
"ErrorCategory",
|
|
211
239
|
"ErrorSanitizer",
|
|
212
240
|
"ErrorSeverity",
|
|
213
241
|
"ExchangeType",
|
|
242
|
+
"FailureCategory",
|
|
214
243
|
"FlowController",
|
|
215
244
|
"FlushItem",
|
|
216
245
|
"FlushReason",
|
|
217
246
|
"FlushReport",
|
|
218
247
|
"GroupFlushReport",
|
|
248
|
+
"HandlerTimeoutError",
|
|
219
249
|
"HandoffState",
|
|
220
250
|
"Header",
|
|
221
251
|
"HealthCheckConfig",
|
|
222
252
|
"HealthStatus",
|
|
223
253
|
"HealthWatcher",
|
|
254
|
+
"InMemoryOffsetStore",
|
|
255
|
+
"InboundMessageTooLargeError",
|
|
256
|
+
"InfrastructureError",
|
|
257
|
+
"InvalidMessageError",
|
|
224
258
|
"JsonParser",
|
|
225
259
|
"LedgerFullError",
|
|
226
260
|
"LoggingConfig",
|
|
@@ -235,7 +269,9 @@ __all__ = [
|
|
|
235
269
|
"MissingDependencyError",
|
|
236
270
|
"NackMessage",
|
|
237
271
|
"OTelTracingMiddleware",
|
|
272
|
+
"OffsetStore",
|
|
238
273
|
"Path",
|
|
274
|
+
"PermanentConnectionError",
|
|
239
275
|
"PolicyTemplate",
|
|
240
276
|
"PoolConfig",
|
|
241
277
|
"PreflightCheck",
|
|
@@ -245,6 +281,7 @@ __all__ = [
|
|
|
245
281
|
"PrometheusCollector",
|
|
246
282
|
"PublishOutcome",
|
|
247
283
|
"PublishPreparer",
|
|
284
|
+
"PublishReason",
|
|
248
285
|
"PublishStatus",
|
|
249
286
|
"PublisherConfig",
|
|
250
287
|
"PydanticDecoder",
|
|
@@ -260,9 +297,11 @@ __all__ = [
|
|
|
260
297
|
"RateLimitConfig",
|
|
261
298
|
"RateLimitMiddleware",
|
|
262
299
|
"RawDecoder",
|
|
300
|
+
"ReconnectBudgetExhaustedError",
|
|
263
301
|
"RejectMessage",
|
|
264
302
|
"RejectWithoutDLXPolicy",
|
|
265
303
|
"ReliabilityProfile",
|
|
304
|
+
"ReplayRequest",
|
|
266
305
|
"ReplayResult",
|
|
267
306
|
"RetryConfig",
|
|
268
307
|
"RetryDisabled",
|
|
@@ -283,14 +322,23 @@ __all__ = [
|
|
|
283
322
|
"SettlementReportError",
|
|
284
323
|
"SocketConfig",
|
|
285
324
|
"StaleGenerationError",
|
|
325
|
+
"StreamConsumerConfig",
|
|
326
|
+
"StreamOffset",
|
|
327
|
+
"StreamOffsetType",
|
|
286
328
|
"SyncBatchPublisher",
|
|
287
329
|
"SyncBroker",
|
|
288
330
|
"SyncWorkerPool",
|
|
331
|
+
"TLSVerificationError",
|
|
332
|
+
"TerminalAction",
|
|
333
|
+
"TerminalConnectionError",
|
|
334
|
+
"TopologyFinding",
|
|
289
335
|
"TopologyMode",
|
|
336
|
+
"TopologyReport",
|
|
290
337
|
"TopologyValidationError",
|
|
291
338
|
"UnknownDeliveryError",
|
|
292
339
|
"UnsafeToBrowseError",
|
|
293
340
|
"UnsafeTopologyError",
|
|
341
|
+
"VhostAccessRefusedError",
|
|
294
342
|
"WorkerConfig",
|
|
295
343
|
"__version__",
|
|
296
344
|
"apply_profile",
|
|
@@ -312,4 +360,5 @@ __all__ = [
|
|
|
312
360
|
"standard_config",
|
|
313
361
|
"start_metrics_server",
|
|
314
362
|
"validate_profile",
|
|
363
|
+
"verify_topology",
|
|
315
364
|
]
|