babelqueue 1.13.0__tar.gz → 1.15.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.
- babelqueue-1.15.0/.github/dependabot.yml +11 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/.github/workflows/ci.yml +2 -1
- {babelqueue-1.13.0 → babelqueue-1.15.0}/CHANGELOG.md +108 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/PKG-INFO +25 -4
- {babelqueue-1.13.0 → babelqueue-1.15.0}/README.md +21 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/pyproject.toml +3 -3
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/__init__.py +11 -2
- babelqueue-1.15.0/src/babelqueue/app.py +528 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/artemis_transport.py +5 -5
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/asb_transport.py +2 -2
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/codec.py +93 -8
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/kafka_transport.py +3 -3
- babelqueue-1.15.0/src/babelqueue/pulsar_transport.py +383 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/redrive.py +13 -9
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/sqs_transport.py +87 -11
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/transport.py +49 -0
- babelqueue-1.15.0/tests/conformance/CONFORMANCE_VERSION +1 -0
- babelqueue-1.15.0/tests/conformance/fixtures/data-array-rejected.json +13 -0
- babelqueue-1.15.0/tests/conformance/fixtures/empty-data-roundtrip.json +13 -0
- babelqueue-1.15.0/tests/conformance/fixtures/forbidden-key-meta-attempts.json +16 -0
- babelqueue-1.15.0/tests/conformance/fixtures/forbidden-key-meta-max-retries.json +16 -0
- babelqueue-1.15.0/tests/conformance/fixtures/forbidden-key-meta-source.json +16 -0
- babelqueue-1.15.0/tests/conformance/fixtures/forbidden-key-meta-ts.json +16 -0
- babelqueue-1.15.0/tests/conformance/fixtures/forbidden-key-timestamp.json +16 -0
- babelqueue-1.15.0/tests/conformance/fixtures/unknown-lang.json +15 -0
- babelqueue-1.15.0/tests/conformance/fixtures/unknown-meta-roundtrip.json +20 -0
- babelqueue-1.15.0/tests/conformance/fixtures/unknown-toplevel-roundtrip.json +16 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/manifest.json +256 -0
- babelqueue-1.15.0/tests/test_app.py +303 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_artemis_transport.py +27 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_asb_transport.py +22 -0
- babelqueue-1.15.0/tests/test_behaviour_conformance.py +195 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_codec.py +27 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_kafka_transport.py +23 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_pulsar_conformance.py +1 -1
- babelqueue-1.15.0/tests/test_pulsar_transport.py +648 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_redrive.py +38 -0
- babelqueue-1.15.0/tests/test_shutdown.py +302 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_sqs_transport.py +245 -3
- babelqueue-1.13.0/src/babelqueue/app.py +0 -252
- babelqueue-1.13.0/src/babelqueue/pulsar_transport.py +0 -202
- babelqueue-1.13.0/tests/test_app.py +0 -125
- babelqueue-1.13.0/tests/test_pulsar_transport.py +0 -215
- {babelqueue-1.13.0 → babelqueue-1.15.0}/.github/FUNDING.yml +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/.github/workflows/release.yml +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/.gitignore +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/LICENSE +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/celery.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/contracts.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/dead_letter.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/django/__init__.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/django/apps.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/django/management/__init__.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/django/management/commands/__init__.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/django/management/commands/babelqueue_worker.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/exceptions.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/gdpr.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/headers.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/idempotency.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/otel.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/outbox.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/pika_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/py.typed +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/redis_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/replay.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/routing.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/src/babelqueue/schema.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/fixtures/dead-lettered.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/fixtures/invalid-missing-urn.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/fixtures/invalid-unknown-schema-version.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/fixtures/order-created.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/fixtures/unicode-and-numbers.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/fixtures/urn-alias.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/conformance/schema/message-envelope.schema.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/fixtures/dead-lettered.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/fixtures/order-created.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_artemis_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_asb_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_celery.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_dead_letter.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_django.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_gdpr.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_headers.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_idempotency.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_kafka_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_otel.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_outbox.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_overhead.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_pika_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_redis_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_replay.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_schema.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_schema_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.15.0}/tests/test_sqs_conformance.py +0 -0
|
@@ -118,7 +118,8 @@ jobs:
|
|
|
118
118
|
- uses: actions/checkout@v5
|
|
119
119
|
- name: Verify vendored conformance matches the canonical suite
|
|
120
120
|
run: |
|
|
121
|
-
git clone --depth 1 https://github.com/BabelQueue/conformance.git "$RUNNER_TEMP/conformance"
|
|
121
|
+
git clone --depth 1 --branch v1.0.0 https://github.com/BabelQueue/conformance.git "$RUNNER_TEMP/conformance"
|
|
122
|
+
diff -u "$RUNNER_TEMP/conformance/CONFORMANCE_VERSION" "tests/conformance/CONFORMANCE_VERSION"
|
|
122
123
|
diff -ru "$RUNNER_TEMP/conformance/manifest.json" "tests/conformance/manifest.json"
|
|
123
124
|
diff -ru "$RUNNER_TEMP/conformance/fixtures" "tests/conformance/fixtures"
|
|
124
125
|
diff -ru "$RUNNER_TEMP/conformance/schema" "tests/conformance/schema"
|
|
@@ -9,6 +9,114 @@ The envelope wire format is versioned separately by `meta.schema_version`
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [1.15.0] - 2026-10-10
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`ReceivedMessage.route_urn` routing seam** (MINOR). An optional field a transport can set
|
|
16
|
+
when its broker carries the URN out of band. When non-empty `BabelQueue.dispatch()` looks the
|
|
17
|
+
handler up by it instead of the decoded body's URN; empty (the default for every existing
|
|
18
|
+
transport) keeps routing on the body. The body is never rewritten, so the handler, the retry
|
|
19
|
+
re-publish and the dead-letter record carry the producer's original body (including a `urn`
|
|
20
|
+
alias). No wire change.
|
|
21
|
+
- **`RoutedPublisher` optional transport capability** (MINOR). `publish_routed(queue, body,
|
|
22
|
+
route_urn)`. When a delivery has a `route_urn` and the transport implements it, the retry, release and dead-letter
|
|
23
|
+
writes, and `redrive`, use it so the broker's routing property keeps the original
|
|
24
|
+
value across attempts (no flip to the body URN on the second delivery). `PulsarTransport`
|
|
25
|
+
implements it (`bq-job` pinned; blank derives from the body). Others keep plain `publish`.
|
|
26
|
+
No wire change.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- `BabelQueue.dispatch()` applies `route_urn` only to a body that decodes; an undecodable body
|
|
30
|
+
stays on the poison / unknown-URN path instead of being routed to a handler.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- **`on_unknown_urn=RELEASE` no longer loops a version-gated or poison message forever.**
|
|
34
|
+
`BabelQueue.dispatch()` re-published such a message unchanged on every delivery (`attempts` never
|
|
35
|
+
grew, so it never reached the DLQ). A delivery a transport version-gated (reserved
|
|
36
|
+
`unsupported-schema-version` `route_urn`, e.g. Pulsar `bq-schema-version`), an undecodable body
|
|
37
|
+
and a body naming no URN now take the bounded `FAIL` path under `RELEASE` (retry up to
|
|
38
|
+
`max_attempts`, then dead-letter or drop), matching the Go core (GR-5). A genuinely unknown URN
|
|
39
|
+
still releases verbatim. No public API or wire change. Note: as on the existing `FAIL` path, a
|
|
40
|
+
body that is not valid JSON is not carried verbatim through the retry / dead-letter re-publish
|
|
41
|
+
(the runtime re-encodes the envelope it could build, so the producer's original bytes are lost);
|
|
42
|
+
previously `RELEASE` re-published those bytes unchanged.
|
|
43
|
+
- **Pulsar consumer version-gates on `bq-schema-version` (broker-bindings §5.7 step 3).**
|
|
44
|
+
`PulsarTransport.pop()` now reads the `bq-schema-version` message property before decoding the
|
|
45
|
+
body. A missing or blank value changes nothing; "Blank" is the same set in every SDK: the empty string or only ASCII whitespace (space, `\t`, `\n`, `\v`, `\f`, `\r`); anything else (NBSP, U+001C-U+001F, U+0085, U+FEFF, U+3000, ...) is not blank and is rejected unless it is exactly the supported version. (`str.strip()` is not used: it also strips U+001C-U+001F, U+0085, NBSP, ...) a value other than the supported
|
|
46
|
+
`SCHEMA_VERSION` (`"2"`, `"x"`, `"01"`, ...) is not decoded: the delivery carries the reserved
|
|
47
|
+
`route_urn` `babelqueue:unsupported-schema-version:<value>`, which no handler owns, so the
|
|
48
|
+
runtime quarantines it through the existing unknown-URN retry / dead-letter path (the error
|
|
49
|
+
names the rejected version). The body is passed through untouched. No wire change. The route
|
|
50
|
+
is an in-process marker only: it also carries the delivery's original `bq-job`, and the retry /
|
|
51
|
+
dead-letter re-publish (`publish_routed`) writes that original `bq-job` (or, when the delivery
|
|
52
|
+
had none, the body's job) and the original raw `bq-schema-version` back, never the sentinel, so
|
|
53
|
+
a redrive meets the same gate. The gate also logs a `babelqueue.pulsar` warning naming the
|
|
54
|
+
rejected version and queue, including for a body that is not valid JSON (which the runtime
|
|
55
|
+
quarantines as a poison message whose error names the reserved route).
|
|
56
|
+
- **Pulsar consumer now follows broker-bindings §5.2 / §5.7.** `PulsarTransport.pop()` routes on
|
|
57
|
+
the `bq-job` message property (set on `ReceivedMessage.route_urn`; the body's URN is used only
|
|
58
|
+
when the property is absent or blank) and takes `attempts` from the authoritative
|
|
59
|
+
`bq-attempts` property (the body's value is used only when the property is absent or not a
|
|
60
|
+
plain ASCII-decimal integer that fits a 32-bit int), then applies `max(.., redelivery_count)`.
|
|
61
|
+
Previously only the body was read. The body's `job` / `urn` is **not** rewritten, so a
|
|
62
|
+
property/body mismatch no longer leaks into retries or the DLQ; `attempts` is written into the
|
|
63
|
+
body only when the body already holds an integer `attempts` and the value changes (a missing
|
|
64
|
+
or non-integer `attempts` is still rejected by `EnvelopeCodec.accepts`). When the property and
|
|
65
|
+
the body name different URNs the property wins and the new optional
|
|
66
|
+
`on_routing_mismatch(property_urn, body_urn, envelope)` constructor hook is called for an
|
|
67
|
+
accepted envelope, after attempts are reconciled (its exceptions are swallowed; it is separate
|
|
68
|
+
from error handling). A body without a URN is still rejected. No wire change
|
|
69
|
+
(`schema_version: 1`).
|
|
70
|
+
|
|
71
|
+
## [1.14.0] - 2026-10-03
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
- **Graceful shutdown** of `BabelQueue.consume()`: on SIGTERM/SIGINT the loop sets a stop flag,
|
|
75
|
+
takes no new message, lets the in-flight handler finish (or releases the message unchanged once
|
|
76
|
+
the new `shutdown_timeout` — default 30 s — expires), restores the previous signal handlers and
|
|
77
|
+
closes the transport. Handlers are installed only on the main thread (`handle_signals=False`
|
|
78
|
+
opts out); a second signal forces an immediate exit with the message released. A plain
|
|
79
|
+
`KeyboardInterrupt` keeps its previous behaviour. New `BabelQueue.stop()` ends the loop from any
|
|
80
|
+
thread after the in-flight message.
|
|
81
|
+
- `Redeliverer` protocol (`redeliver(message, body, delay)`) — a transport's native
|
|
82
|
+
release; the runtime uses it for retries, unknown-URN `RELEASE` and shutdown releases, and
|
|
83
|
+
falls back to publish + ack otherwise. New `retry_backoff` / `unknown_urn_release_delay`
|
|
84
|
+
settings feed its delay.
|
|
85
|
+
- Conformance runners for the `roundtrip`, `data_shape`, `forbidden_keys` and
|
|
86
|
+
`payload_schema_unicode` sections (none skipped).
|
|
87
|
+
- `.github/dependabot.yml` (pip + GitHub Actions, weekly).
|
|
88
|
+
|
|
89
|
+
### Changed
|
|
90
|
+
- **SQS release** (broker-bindings §3.5): retries, unknown-URN `RELEASE` and shutdown releases
|
|
91
|
+
**always** call `ChangeMessageVisibility` instead of re-sending, so the broker's
|
|
92
|
+
`ApproximateReceiveCount` carries the attempt count. There is no send + delete fallback. The
|
|
93
|
+
default delay for `retry_backoff` and `unknown_urn_release_delay` is **0 s** (immediate
|
|
94
|
+
redelivery); a delay outside 0..43200 s (including `inf`/`nan`) is clamped with a warning on the
|
|
95
|
+
`babelqueue.sqs` logger. A message without a receipt handle is not released (warning); one
|
|
96
|
+
without a receive count is released but its attempts cannot advance (warning).
|
|
97
|
+
**Poison-loop risk:** a message that always fails is redelivered immediately until
|
|
98
|
+
`max_attempts` — configure a native SQS `RedrivePolicy` (`maxReceiveCount` ≥ `max_attempts`) as
|
|
99
|
+
the backstop and a non-zero `retry_backoff` where needed. A shutdown release also counts as a
|
|
100
|
+
receive on SQS, so keep `shutdown_timeout` above the longest handler time.
|
|
101
|
+
- Delivery semantics are documented as **at-least-once**: a handler that finishes right as the
|
|
102
|
+
shutdown deadline fires may be released and run again.
|
|
103
|
+
- The SQS, Kafka, Pulsar, Azure Service Bus and Artemis transports' header/property projections
|
|
104
|
+
read the envelope without logging a false forbidden-key drop (the body is sent unchanged).
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
- The five forbidden envelope keys (`timestamp`, `meta.max_retries`, `meta.attempts`,
|
|
108
|
+
`meta.source`, `meta.ts` — message-envelope §10) are dropped on decode with a warning on the
|
|
109
|
+
`babelqueue.codec` logger, and never written on encode. **Transitional:** encode currently drops
|
|
110
|
+
them with a warning; a future MINOR (K-15 / R1-E0) will reject them instead. A raw-body release
|
|
111
|
+
republish now strips them too (a clean body is re-sent byte for byte); DLQ redrive still restores
|
|
112
|
+
the original bytes unchanged.
|
|
113
|
+
- `BabelQueue.stop()` called before `consume()` starts is now honoured instead of being cleared.
|
|
114
|
+
- A failed acknowledgement of an already-processed message (e.g. SQS `DeleteMessage` after a
|
|
115
|
+
successful handler, an unknown-URN `DELETE`, or a dead-letter publish) is no longer treated as a
|
|
116
|
+
handler failure: the message is **not** released or retried; the failure is logged at `ERROR`
|
|
117
|
+
on the new `babelqueue.app` logger and the broker redelivers it on its own (SQS: after the
|
|
118
|
+
visibility timeout). The consume loop keeps running.
|
|
119
|
+
|
|
12
120
|
## [1.13.0] - 2026-06-21
|
|
13
121
|
|
|
14
122
|
### Added
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: babelqueue
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.0
|
|
4
4
|
Summary: Polyglot Queues, Simplified — the Python core: the canonical BabelQueue wire-envelope codec, contracts and dead-letter helpers.
|
|
5
5
|
Project-URL: Homepage, https://babelqueue.com
|
|
6
6
|
Project-URL: Source, https://github.com/BabelQueue/babelqueue-python
|
|
7
7
|
Project-URL: Issues, https://github.com/BabelQueue/babelqueue-python/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/BabelQueue/babelqueue-python/blob/main/CHANGELOG.md
|
|
9
|
-
Author-email: Muhammet Şafak <info@muhammetsafak.com
|
|
9
|
+
Author-email: Muhammet Şafak <info@muhammetsafak.com>
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Keywords: envelope,json,messaging,microservices,polyglot,queue
|
|
@@ -35,7 +35,7 @@ Provides-Extra: dev
|
|
|
35
35
|
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
36
36
|
Requires-Dist: pytest-cov>=4; extra == 'dev'
|
|
37
37
|
Requires-Dist: pytest>=7; extra == 'dev'
|
|
38
|
-
Requires-Dist: ruff
|
|
38
|
+
Requires-Dist: ruff<0.16,>=0.5; extra == 'dev'
|
|
39
39
|
Provides-Extra: django
|
|
40
40
|
Requires-Dist: django>=4.2; extra == 'django'
|
|
41
41
|
Provides-Extra: gdpr
|
|
@@ -174,6 +174,27 @@ app.run() # consume forever (Ctrl-C to stop)
|
|
|
174
174
|
and `memory://` (in-process, great for tests/local). Bring your own by passing
|
|
175
175
|
`transport=...`.
|
|
176
176
|
|
|
177
|
+
- **Graceful shutdown:** SIGTERM/SIGINT finish the in-flight handler, or release
|
|
178
|
+
the message unchanged once `shutdown_timeout` expires. Delivery is
|
|
179
|
+
**at-least-once** — keep handlers idempotent (see the idempotency helper).
|
|
180
|
+
|
|
181
|
+
### SQS release and poison messages
|
|
182
|
+
|
|
183
|
+
On a handler failure, unknown-URN `release` or shutdown release, the SQS
|
|
184
|
+
transport **always** releases with `ChangeMessageVisibility` (broker-bindings
|
|
185
|
+
§3.5) — it never re-sends a copy. The broker's `ApproximateReceiveCount` is the
|
|
186
|
+
attempt counter, so a shutdown release also consumes an attempt. The default
|
|
187
|
+
delay is **0 s** (`retry_backoff` / `unknown_urn_release_delay`), i.e. the
|
|
188
|
+
message is visible again immediately; delays outside 0..43200 s are clamped with
|
|
189
|
+
a warning.
|
|
190
|
+
|
|
191
|
+
**Poison-loop risk:** a message that always fails is redelivered at once until
|
|
192
|
+
`max_attempts` is reached. If the receive count is unavailable the SDK cannot
|
|
193
|
+
advance attempts at all. **Configure a native `RedrivePolicy`**
|
|
194
|
+
(`maxReceiveCount` ≥ `max_attempts`, pointing at `<queue>.dlq`) on every SQS
|
|
195
|
+
queue as the broker-side backstop, and set a non-zero `retry_backoff` if the
|
|
196
|
+
handler's dependencies need time to recover.
|
|
197
|
+
|
|
177
198
|
### Sharing a Redis queue with Laravel
|
|
178
199
|
|
|
179
200
|
By default the Redis transport owns its queue end-to-end (`RPUSH` to produce;
|
|
@@ -120,6 +120,27 @@ app.run() # consume forever (Ctrl-C to stop)
|
|
|
120
120
|
and `memory://` (in-process, great for tests/local). Bring your own by passing
|
|
121
121
|
`transport=...`.
|
|
122
122
|
|
|
123
|
+
- **Graceful shutdown:** SIGTERM/SIGINT finish the in-flight handler, or release
|
|
124
|
+
the message unchanged once `shutdown_timeout` expires. Delivery is
|
|
125
|
+
**at-least-once** — keep handlers idempotent (see the idempotency helper).
|
|
126
|
+
|
|
127
|
+
### SQS release and poison messages
|
|
128
|
+
|
|
129
|
+
On a handler failure, unknown-URN `release` or shutdown release, the SQS
|
|
130
|
+
transport **always** releases with `ChangeMessageVisibility` (broker-bindings
|
|
131
|
+
§3.5) — it never re-sends a copy. The broker's `ApproximateReceiveCount` is the
|
|
132
|
+
attempt counter, so a shutdown release also consumes an attempt. The default
|
|
133
|
+
delay is **0 s** (`retry_backoff` / `unknown_urn_release_delay`), i.e. the
|
|
134
|
+
message is visible again immediately; delays outside 0..43200 s are clamped with
|
|
135
|
+
a warning.
|
|
136
|
+
|
|
137
|
+
**Poison-loop risk:** a message that always fails is redelivered at once until
|
|
138
|
+
`max_attempts` is reached. If the receive count is unavailable the SDK cannot
|
|
139
|
+
advance attempts at all. **Configure a native `RedrivePolicy`**
|
|
140
|
+
(`maxReceiveCount` ≥ `max_attempts`, pointing at `<queue>.dlq`) on every SQS
|
|
141
|
+
queue as the broker-side backstop, and set a non-zero `retry_backoff` if the
|
|
142
|
+
handler's dependencies need time to recover.
|
|
143
|
+
|
|
123
144
|
### Sharing a Redis queue with Laravel
|
|
124
145
|
|
|
125
146
|
By default the Redis transport owns its queue end-to-end (`RPUSH` to produce;
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "babelqueue"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.15.0"
|
|
8
8
|
description = "Polyglot Queues, Simplified — the Python core: the canonical BabelQueue wire-envelope codec, contracts and dead-letter helpers."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
|
-
authors = [{ name = "Muhammet Şafak", email = "info@muhammetsafak.com
|
|
13
|
+
authors = [{ name = "Muhammet Şafak", email = "info@muhammetsafak.com" }]
|
|
14
14
|
keywords = ["queue", "polyglot", "microservices", "json", "envelope", "messaging"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 4 - Beta",
|
|
@@ -42,7 +42,7 @@ otel = ["opentelemetry-api>=1.20"]
|
|
|
42
42
|
gdpr = ["cryptography>=42"]
|
|
43
43
|
celery = ["celery>=5"]
|
|
44
44
|
django = ["django>=4.2"]
|
|
45
|
-
dev = ["pytest>=7", "pytest-cov>=4", "mypy>=1.8", "ruff>=0.5"]
|
|
45
|
+
dev = ["pytest>=7", "pytest-cov>=4", "mypy>=1.8", "ruff>=0.5,<0.16"]
|
|
46
46
|
|
|
47
47
|
[project.urls]
|
|
48
48
|
Homepage = "https://babelqueue.com"
|
|
@@ -29,9 +29,16 @@ from .outbox import (
|
|
|
29
29
|
from .exceptions import BabelQueueError, DecryptError, UnknownUrnError
|
|
30
30
|
from .replay import HEADER_REPLAY_BYPASS, bypass_external_effects, is_replay
|
|
31
31
|
from .routing import UnknownUrnStrategy
|
|
32
|
-
from .transport import
|
|
32
|
+
from .transport import (
|
|
33
|
+
HeaderPublisher,
|
|
34
|
+
InMemoryTransport,
|
|
35
|
+
ReceivedMessage,
|
|
36
|
+
Redeliverer,
|
|
37
|
+
RoutedPublisher,
|
|
38
|
+
Transport,
|
|
39
|
+
)
|
|
33
40
|
|
|
34
|
-
__version__ = "1.
|
|
41
|
+
__version__ = "1.15.0"
|
|
35
42
|
|
|
36
43
|
__all__ = [
|
|
37
44
|
"BabelQueue",
|
|
@@ -45,6 +52,8 @@ __all__ = [
|
|
|
45
52
|
"InMemoryTransport",
|
|
46
53
|
"ReceivedMessage",
|
|
47
54
|
"HeaderPublisher",
|
|
55
|
+
"Redeliverer",
|
|
56
|
+
"RoutedPublisher",
|
|
48
57
|
"BabelQueueError",
|
|
49
58
|
"UnknownUrnError",
|
|
50
59
|
"DecryptError",
|