babelqueue 1.13.0__tar.gz → 1.14.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.14.0/.github/dependabot.yml +11 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/.github/workflows/ci.yml +2 -1
- {babelqueue-1.13.0 → babelqueue-1.14.0}/CHANGELOG.md +49 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/PKG-INFO +24 -3
- {babelqueue-1.13.0 → babelqueue-1.14.0}/README.md +21 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/pyproject.toml +2 -2
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/__init__.py +3 -2
- babelqueue-1.14.0/src/babelqueue/app.py +502 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/artemis_transport.py +5 -5
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/asb_transport.py +2 -2
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/codec.py +93 -8
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/kafka_transport.py +3 -3
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/pulsar_transport.py +2 -2
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/sqs_transport.py +87 -11
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/transport.py +19 -0
- babelqueue-1.14.0/tests/conformance/CONFORMANCE_VERSION +1 -0
- babelqueue-1.14.0/tests/conformance/fixtures/data-array-rejected.json +13 -0
- babelqueue-1.14.0/tests/conformance/fixtures/empty-data-roundtrip.json +13 -0
- babelqueue-1.14.0/tests/conformance/fixtures/forbidden-key-meta-attempts.json +16 -0
- babelqueue-1.14.0/tests/conformance/fixtures/forbidden-key-meta-max-retries.json +16 -0
- babelqueue-1.14.0/tests/conformance/fixtures/forbidden-key-meta-source.json +16 -0
- babelqueue-1.14.0/tests/conformance/fixtures/forbidden-key-meta-ts.json +16 -0
- babelqueue-1.14.0/tests/conformance/fixtures/forbidden-key-timestamp.json +16 -0
- babelqueue-1.14.0/tests/conformance/fixtures/unknown-lang.json +15 -0
- babelqueue-1.14.0/tests/conformance/fixtures/unknown-meta-roundtrip.json +20 -0
- babelqueue-1.14.0/tests/conformance/fixtures/unknown-toplevel-roundtrip.json +16 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/manifest.json +256 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_artemis_transport.py +27 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_asb_transport.py +22 -0
- babelqueue-1.14.0/tests/test_behaviour_conformance.py +195 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_codec.py +27 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_kafka_transport.py +23 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_pulsar_transport.py +25 -0
- babelqueue-1.14.0/tests/test_shutdown.py +302 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_sqs_transport.py +245 -3
- babelqueue-1.13.0/src/babelqueue/app.py +0 -252
- {babelqueue-1.13.0 → babelqueue-1.14.0}/.github/FUNDING.yml +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/.github/workflows/release.yml +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/.gitignore +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/LICENSE +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/celery.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/contracts.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/dead_letter.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/django/__init__.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/django/apps.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/django/management/__init__.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/django/management/commands/__init__.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/django/management/commands/babelqueue_worker.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/exceptions.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/gdpr.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/headers.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/idempotency.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/otel.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/outbox.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/pika_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/py.typed +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/redis_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/redrive.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/replay.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/routing.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/src/babelqueue/schema.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/fixtures/dead-lettered.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/fixtures/invalid-missing-urn.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/fixtures/invalid-unknown-schema-version.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/fixtures/order-created.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/fixtures/unicode-and-numbers.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/fixtures/urn-alias.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/conformance/schema/message-envelope.schema.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/fixtures/dead-lettered.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/fixtures/order-created.json +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_app.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_artemis_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_asb_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_celery.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_dead_letter.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_django.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_gdpr.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_headers.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_idempotency.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_kafka_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_otel.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_outbox.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_overhead.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_pika_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_pulsar_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_redis_transport.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_redrive.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_replay.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_schema.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.0}/tests/test_schema_conformance.py +0 -0
- {babelqueue-1.13.0 → babelqueue-1.14.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,55 @@ The envelope wire format is versioned separately by `meta.schema_version`
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [1.14.0] - 2026-10-03
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Graceful shutdown** of `BabelQueue.consume()`: on SIGTERM/SIGINT the loop sets a stop flag,
|
|
16
|
+
takes no new message, lets the in-flight handler finish (or releases the message unchanged once
|
|
17
|
+
the new `shutdown_timeout` — default 30 s — expires), restores the previous signal handlers and
|
|
18
|
+
closes the transport. Handlers are installed only on the main thread (`handle_signals=False`
|
|
19
|
+
opts out); a second signal forces an immediate exit with the message released. A plain
|
|
20
|
+
`KeyboardInterrupt` keeps its previous behaviour. New `BabelQueue.stop()` ends the loop from any
|
|
21
|
+
thread after the in-flight message.
|
|
22
|
+
- `Redeliverer` protocol (`redeliver(message, body, delay)`) — a transport's native
|
|
23
|
+
release; the runtime uses it for retries, unknown-URN `RELEASE` and shutdown releases, and
|
|
24
|
+
falls back to publish + ack otherwise. New `retry_backoff` / `unknown_urn_release_delay`
|
|
25
|
+
settings feed its delay.
|
|
26
|
+
- Conformance runners for the `roundtrip`, `data_shape`, `forbidden_keys` and
|
|
27
|
+
`payload_schema_unicode` sections (none skipped).
|
|
28
|
+
- `.github/dependabot.yml` (pip + GitHub Actions, weekly).
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- **SQS release** (broker-bindings §3.5): retries, unknown-URN `RELEASE` and shutdown releases
|
|
32
|
+
**always** call `ChangeMessageVisibility` instead of re-sending, so the broker's
|
|
33
|
+
`ApproximateReceiveCount` carries the attempt count. There is no send + delete fallback. The
|
|
34
|
+
default delay for `retry_backoff` and `unknown_urn_release_delay` is **0 s** (immediate
|
|
35
|
+
redelivery); a delay outside 0..43200 s (including `inf`/`nan`) is clamped with a warning on the
|
|
36
|
+
`babelqueue.sqs` logger. A message without a receipt handle is not released (warning); one
|
|
37
|
+
without a receive count is released but its attempts cannot advance (warning).
|
|
38
|
+
**Poison-loop risk:** a message that always fails is redelivered immediately until
|
|
39
|
+
`max_attempts` — configure a native SQS `RedrivePolicy` (`maxReceiveCount` ≥ `max_attempts`) as
|
|
40
|
+
the backstop and a non-zero `retry_backoff` where needed. A shutdown release also counts as a
|
|
41
|
+
receive on SQS, so keep `shutdown_timeout` above the longest handler time.
|
|
42
|
+
- Delivery semantics are documented as **at-least-once**: a handler that finishes right as the
|
|
43
|
+
shutdown deadline fires may be released and run again.
|
|
44
|
+
- The SQS, Kafka, Pulsar, Azure Service Bus and Artemis transports' header/property projections
|
|
45
|
+
read the envelope without logging a false forbidden-key drop (the body is sent unchanged).
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- The five forbidden envelope keys (`timestamp`, `meta.max_retries`, `meta.attempts`,
|
|
49
|
+
`meta.source`, `meta.ts` — message-envelope §10) are dropped on decode with a warning on the
|
|
50
|
+
`babelqueue.codec` logger, and never written on encode. **Transitional:** encode currently drops
|
|
51
|
+
them with a warning; a future MINOR (K-15 / R1-E0) will reject them instead. A raw-body release
|
|
52
|
+
republish now strips them too (a clean body is re-sent byte for byte); DLQ redrive still restores
|
|
53
|
+
the original bytes unchanged.
|
|
54
|
+
- `BabelQueue.stop()` called before `consume()` starts is now honoured instead of being cleared.
|
|
55
|
+
- A failed acknowledgement of an already-processed message (e.g. SQS `DeleteMessage` after a
|
|
56
|
+
successful handler, an unknown-URN `DELETE`, or a dead-letter publish) is no longer treated as a
|
|
57
|
+
handler failure: the message is **not** released or retried; the failure is logged at `ERROR`
|
|
58
|
+
on the new `babelqueue.app` logger and the broker redelivers it on its own (SQS: after the
|
|
59
|
+
visibility timeout). The consume loop keeps running.
|
|
60
|
+
|
|
12
61
|
## [1.13.0] - 2026-06-21
|
|
13
62
|
|
|
14
63
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: babelqueue
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.14.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
|
|
@@ -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,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "babelqueue"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.14.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"
|
|
@@ -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,9 @@ 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 HeaderPublisher, InMemoryTransport, ReceivedMessage, Transport
|
|
32
|
+
from .transport import HeaderPublisher, InMemoryTransport, ReceivedMessage, Redeliverer, Transport
|
|
33
33
|
|
|
34
|
-
__version__ = "1.
|
|
34
|
+
__version__ = "1.14.0"
|
|
35
35
|
|
|
36
36
|
__all__ = [
|
|
37
37
|
"BabelQueue",
|
|
@@ -45,6 +45,7 @@ __all__ = [
|
|
|
45
45
|
"InMemoryTransport",
|
|
46
46
|
"ReceivedMessage",
|
|
47
47
|
"HeaderPublisher",
|
|
48
|
+
"Redeliverer",
|
|
48
49
|
"BabelQueueError",
|
|
49
50
|
"UnknownUrnError",
|
|
50
51
|
"DecryptError",
|