babelqueue 0.3.0__tar.gz → 0.4.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-0.3.0 → babelqueue-0.4.0}/CHANGELOG.md +14 -1
- {babelqueue-0.3.0 → babelqueue-0.4.0}/PKG-INFO +1 -1
- {babelqueue-0.3.0 → babelqueue-0.4.0}/pyproject.toml +1 -1
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/__init__.py +1 -1
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/codec.py +33 -0
- babelqueue-0.4.0/tests/conformance/fixtures/invalid-missing-urn.json +14 -0
- babelqueue-0.4.0/tests/conformance/fixtures/invalid-unknown-schema-version.json +15 -0
- babelqueue-0.4.0/tests/conformance/fixtures/unicode-and-numbers.json +20 -0
- babelqueue-0.4.0/tests/conformance/fixtures/urn-alias.json +15 -0
- babelqueue-0.4.0/tests/conformance/manifest.json +71 -0
- babelqueue-0.4.0/tests/conformance/schema/message-envelope.schema.json +110 -0
- babelqueue-0.4.0/tests/fixtures/dead-lettered.json +24 -0
- babelqueue-0.4.0/tests/fixtures/order-created.json +15 -0
- babelqueue-0.4.0/tests/test_conformance.py +58 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/.github/workflows/ci.yml +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/.github/workflows/release.yml +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/.gitignore +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/LICENSE +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/README.md +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/app.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/contracts.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/dead_letter.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/exceptions.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/pika_transport.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/py.typed +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/redis_transport.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/routing.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/src/babelqueue/transport.py +0 -0
- {babelqueue-0.3.0/tests → babelqueue-0.4.0/tests/conformance}/fixtures/dead-lettered.json +0 -0
- {babelqueue-0.3.0/tests → babelqueue-0.4.0/tests/conformance}/fixtures/order-created.json +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/tests/test_app.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/tests/test_codec.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/tests/test_dead_letter.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/tests/test_pika_transport.py +0 -0
- {babelqueue-0.3.0 → babelqueue-0.4.0}/tests/test_redis_transport.py +0 -0
|
@@ -9,6 +9,17 @@ The envelope wire format is versioned separately by `meta.schema_version`
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.4.0] - 2026-06-06
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `EnvelopeCodec.urn()` — resolve the URN (`job`, accepting `urn` as an alias).
|
|
16
|
+
- `EnvelopeCodec.accepts()` — consumer-side envelope validation (rejects empty URN,
|
|
17
|
+
unsupported `meta.schema_version`, blank `trace_id`, non-object `data`).
|
|
18
|
+
- Shared **cross-SDK conformance suite** under `tests/conformance/` (vendored from
|
|
19
|
+
the canonical `conformance/` set) plus a `test_conformance.py` runner.
|
|
20
|
+
|
|
21
|
+
## [0.3.0] - 2026-06-06
|
|
22
|
+
|
|
12
23
|
### Added
|
|
13
24
|
- **RabbitMQ transport** (`PikaTransport`, `amqp://`): durable queue, persistent
|
|
14
25
|
delivery, `basic_get` + manual ack, and the contract AMQP properties (`type`=URN,
|
|
@@ -44,6 +55,8 @@ The envelope wire format is versioned separately by `meta.schema_version`
|
|
|
44
55
|
- Pre-1.0: the public API may change before the `1.0.0` tag.
|
|
45
56
|
- The core has **zero runtime dependencies** (standard library only); Python `>=3.9`.
|
|
46
57
|
|
|
47
|
-
[Unreleased]: https://github.com/BabelQueue/babelqueue-python/compare/v0.
|
|
58
|
+
[Unreleased]: https://github.com/BabelQueue/babelqueue-python/compare/v0.4.0...HEAD
|
|
59
|
+
[0.4.0]: https://github.com/BabelQueue/babelqueue-python/compare/v0.3.0...v0.4.0
|
|
60
|
+
[0.3.0]: https://github.com/BabelQueue/babelqueue-python/compare/v0.2.0...v0.3.0
|
|
48
61
|
[0.2.0]: https://github.com/BabelQueue/babelqueue-python/compare/v0.1.0...v0.2.0
|
|
49
62
|
[0.1.0]: https://github.com/BabelQueue/babelqueue-python/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: babelqueue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "babelqueue"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.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"
|
|
@@ -94,3 +94,36 @@ class EnvelopeCodec:
|
|
|
94
94
|
except (ValueError, TypeError):
|
|
95
95
|
return {}
|
|
96
96
|
return decoded if isinstance(decoded, dict) else {}
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def urn(envelope: Mapping[str, Any]) -> str:
|
|
100
|
+
"""The message URN: canonical ``job``, with ``urn`` accepted as an alias."""
|
|
101
|
+
return str(envelope.get("job") or envelope.get("urn") or "")
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def accepts(envelope: Mapping[str, Any]) -> bool:
|
|
105
|
+
"""Whether a consumer should accept this envelope (consumer-side validation).
|
|
106
|
+
|
|
107
|
+
Rejects messages with no URN, an unsupported ``meta.schema_version``, a
|
|
108
|
+
missing/blank ``trace_id``, or a non-object ``data`` / non-integer
|
|
109
|
+
``attempts``. (Accepts the ``urn`` alias, unlike the producer JSON Schema.)
|
|
110
|
+
"""
|
|
111
|
+
if EnvelopeCodec.urn(envelope) == "":
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
meta = envelope.get("meta")
|
|
115
|
+
if not isinstance(meta, dict) or meta.get("schema_version") != SCHEMA_VERSION:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
if not isinstance(envelope.get("data"), dict):
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
attempts = envelope.get("attempts")
|
|
122
|
+
if not isinstance(attempts, int) or isinstance(attempts, bool):
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
trace_id = envelope.get("trace_id")
|
|
126
|
+
if not isinstance(trace_id, str) or trace_id == "":
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
return True
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"trace_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
|
|
3
|
+
"data": {
|
|
4
|
+
"order_id": 1042
|
|
5
|
+
},
|
|
6
|
+
"meta": {
|
|
7
|
+
"id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
|
|
8
|
+
"queue": "orders",
|
|
9
|
+
"lang": "php",
|
|
10
|
+
"schema_version": 1,
|
|
11
|
+
"created_at": 1749132727000
|
|
12
|
+
},
|
|
13
|
+
"attempts": 0
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"job": "urn:babel:orders:created",
|
|
3
|
+
"trace_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
|
|
4
|
+
"data": {
|
|
5
|
+
"order_id": 1042
|
|
6
|
+
},
|
|
7
|
+
"meta": {
|
|
8
|
+
"id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
|
|
9
|
+
"queue": "orders",
|
|
10
|
+
"lang": "php",
|
|
11
|
+
"schema_version": 2,
|
|
12
|
+
"created_at": 1749132727000
|
|
13
|
+
},
|
|
14
|
+
"attempts": 0
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"job": "urn:babel:catalog:item.indexed",
|
|
3
|
+
"trace_id": "3f7a1d2e-9b4c-4a8d-bc1e-0f5a6b7c8d90",
|
|
4
|
+
"data": {
|
|
5
|
+
"title": "Café — naïve ☕",
|
|
6
|
+
"qty": 7,
|
|
7
|
+
"price_cents": 1299,
|
|
8
|
+
"ratio": 0.5,
|
|
9
|
+
"active": true,
|
|
10
|
+
"note": null
|
|
11
|
+
},
|
|
12
|
+
"meta": {
|
|
13
|
+
"id": "b2c3d4e5-f607-4890-a1b2-c3d4e5f60718",
|
|
14
|
+
"queue": "catalog",
|
|
15
|
+
"lang": "python",
|
|
16
|
+
"schema_version": 1,
|
|
17
|
+
"created_at": 1749132727000
|
|
18
|
+
},
|
|
19
|
+
"attempts": 2
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"urn": "urn:babel:orders:created",
|
|
3
|
+
"trace_id": "9c1e0b44-7a2d-4e6f-8a10-2b3c4d5e6f70",
|
|
4
|
+
"data": {
|
|
5
|
+
"order_id": 1042
|
|
6
|
+
},
|
|
7
|
+
"meta": {
|
|
8
|
+
"id": "a1b2c3d4-e5f6-4789-90ab-cdef01234567",
|
|
9
|
+
"queue": "orders",
|
|
10
|
+
"lang": "go",
|
|
11
|
+
"schema_version": 1,
|
|
12
|
+
"created_at": 1749132727000
|
|
13
|
+
},
|
|
14
|
+
"attempts": 0
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"description": "Cross-SDK conformance cases. Every BabelQueue SDK core must satisfy these against the canonical wire envelope. Per-message fields (meta.id, trace_id, meta.created_at) are intrinsically unique and are NOT asserted by value.",
|
|
4
|
+
"cases": [
|
|
5
|
+
{
|
|
6
|
+
"name": "order-created",
|
|
7
|
+
"file": "fixtures/order-created.json",
|
|
8
|
+
"valid": true,
|
|
9
|
+
"description": "A normal produced envelope.",
|
|
10
|
+
"expect": {
|
|
11
|
+
"urn": "urn:babel:orders:created",
|
|
12
|
+
"data": { "order_id": 1042 },
|
|
13
|
+
"attempts": 0,
|
|
14
|
+
"lang": "php",
|
|
15
|
+
"schema_version": 1
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "urn-alias",
|
|
20
|
+
"file": "fixtures/urn-alias.json",
|
|
21
|
+
"valid": true,
|
|
22
|
+
"description": "Consumers MUST accept 'urn' as an inbound alias for 'job'.",
|
|
23
|
+
"expect": {
|
|
24
|
+
"urn": "urn:babel:orders:created",
|
|
25
|
+
"data": { "order_id": 1042 },
|
|
26
|
+
"attempts": 0,
|
|
27
|
+
"lang": "go",
|
|
28
|
+
"schema_version": 1
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "dead-lettered",
|
|
33
|
+
"file": "fixtures/dead-lettered.json",
|
|
34
|
+
"valid": true,
|
|
35
|
+
"description": "A dead-lettered message: original preserved + additive dead_letter block.",
|
|
36
|
+
"expect": {
|
|
37
|
+
"urn": "urn:babel:orders:created",
|
|
38
|
+
"data": { "order_id": 1042 },
|
|
39
|
+
"attempts": 3,
|
|
40
|
+
"lang": "php",
|
|
41
|
+
"schema_version": 1,
|
|
42
|
+
"dead_letter": { "reason": "failed", "original_queue": "orders" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "unicode-and-numbers",
|
|
47
|
+
"file": "fixtures/unicode-and-numbers.json",
|
|
48
|
+
"valid": true,
|
|
49
|
+
"description": "UTF-8 strings, integers, an exact float, boolean and null round-trip identically.",
|
|
50
|
+
"expect": {
|
|
51
|
+
"urn": "urn:babel:catalog:item.indexed",
|
|
52
|
+
"data": { "title": "Café — naïve ☕", "qty": 7, "price_cents": 1299, "ratio": 0.5, "active": true, "note": null },
|
|
53
|
+
"attempts": 2,
|
|
54
|
+
"lang": "python",
|
|
55
|
+
"schema_version": 1
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "invalid-unknown-schema-version",
|
|
60
|
+
"file": "fixtures/invalid-unknown-schema-version.json",
|
|
61
|
+
"valid": false,
|
|
62
|
+
"reason": "meta.schema_version is not a version this SDK supports"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "invalid-missing-urn",
|
|
66
|
+
"file": "fixtures/invalid-missing-urn.json",
|
|
67
|
+
"valid": false,
|
|
68
|
+
"reason": "no 'job' or 'urn' — the message has no identity"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://babelqueue.com/contracts/message-envelope.schema.json",
|
|
4
|
+
"title": "BabelQueueMessageEnvelope",
|
|
5
|
+
"description": "The canonical, language-agnostic BabelQueue wire envelope (schema_version 1). This schema is authoritative alongside contracts/message-envelope.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"required": ["job", "trace_id", "data", "meta", "attempts"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"job": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "The message URN — language-agnostic identity. Canonical producer field name. Consumers also accept 'urn' as an inbound alias. Never a class name.",
|
|
14
|
+
"examples": ["urn:babel:orders:created", "urn:babel:orders:invoice.requested"]
|
|
15
|
+
},
|
|
16
|
+
"urn": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 1,
|
|
19
|
+
"description": "Inbound alias for 'job'. Accepted by consumers for interoperability; producers SHOULD emit 'job'."
|
|
20
|
+
},
|
|
21
|
+
"trace_id": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"format": "uuid",
|
|
24
|
+
"description": "Cross-service correlation id. Generated by the first producer, preserved and forwarded unchanged by every SDK across every hop."
|
|
25
|
+
},
|
|
26
|
+
"data": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"description": "Pure, JSON-encodable business payload. No language-specific types. Numbers/time/binary per contracts/message-envelope.md section 6.",
|
|
29
|
+
"additionalProperties": true
|
|
30
|
+
},
|
|
31
|
+
"meta": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"description": "Producer-set, immutable descriptive metadata.",
|
|
34
|
+
"additionalProperties": true,
|
|
35
|
+
"required": ["id", "queue", "lang", "schema_version", "created_at"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"id": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"format": "uuid",
|
|
40
|
+
"description": "Unique id for THIS message (distinct from trace_id)."
|
|
41
|
+
},
|
|
42
|
+
"queue": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"minLength": 1,
|
|
45
|
+
"description": "Logical queue name (not the broker key)."
|
|
46
|
+
},
|
|
47
|
+
"lang": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": ["php", "go", "python", "java", "dotnet", "node"],
|
|
50
|
+
"description": "Producer language tag."
|
|
51
|
+
},
|
|
52
|
+
"schema_version": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"const": 1,
|
|
55
|
+
"description": "Envelope schema version. Consumers MUST reject versions they do not support."
|
|
56
|
+
},
|
|
57
|
+
"created_at": {
|
|
58
|
+
"type": "integer",
|
|
59
|
+
"minimum": 0,
|
|
60
|
+
"description": "Production time as Unix epoch MILLISECONDS, UTC."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"attempts": {
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"minimum": 0,
|
|
67
|
+
"description": "Top-level transport retry counter, mutated by the broker/worker. Deliberately kept OUT of the immutable meta block."
|
|
68
|
+
},
|
|
69
|
+
"dead_letter": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"description": "Optional. Present ONLY on messages sitting on a dead-letter queue (ADR-0009). Additive, so it does not change schema_version. Normal consumers ignore it.",
|
|
72
|
+
"additionalProperties": true,
|
|
73
|
+
"required": ["reason", "failed_at", "original_queue", "attempts"],
|
|
74
|
+
"properties": {
|
|
75
|
+
"reason": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": ["failed", "unknown_urn", "poison"],
|
|
78
|
+
"description": "Why the message was dead-lettered."
|
|
79
|
+
},
|
|
80
|
+
"error": {
|
|
81
|
+
"type": ["string", "null"],
|
|
82
|
+
"description": "Human-readable error message, if any."
|
|
83
|
+
},
|
|
84
|
+
"exception": {
|
|
85
|
+
"type": ["string", "null"],
|
|
86
|
+
"description": "Producer-language exception/type name, if any (informational, language-specific)."
|
|
87
|
+
},
|
|
88
|
+
"failed_at": {
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"minimum": 0,
|
|
91
|
+
"description": "Dead-letter time as Unix epoch milliseconds, UTC."
|
|
92
|
+
},
|
|
93
|
+
"original_queue": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "The queue the message was consumed from before dead-lettering."
|
|
96
|
+
},
|
|
97
|
+
"attempts": {
|
|
98
|
+
"type": "integer",
|
|
99
|
+
"minimum": 0,
|
|
100
|
+
"description": "Delivery attempts made before dead-lettering."
|
|
101
|
+
},
|
|
102
|
+
"lang": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"enum": ["php", "go", "python", "java", "dotnet", "node"],
|
|
105
|
+
"description": "Language of the SDK that dead-lettered the message."
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"job": "urn:babel:orders:created",
|
|
3
|
+
"trace_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
|
|
4
|
+
"data": {
|
|
5
|
+
"order_id": 1042
|
|
6
|
+
},
|
|
7
|
+
"meta": {
|
|
8
|
+
"id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
|
|
9
|
+
"queue": "orders",
|
|
10
|
+
"lang": "php",
|
|
11
|
+
"schema_version": 1,
|
|
12
|
+
"created_at": 1749132727000
|
|
13
|
+
},
|
|
14
|
+
"attempts": 3,
|
|
15
|
+
"dead_letter": {
|
|
16
|
+
"reason": "failed",
|
|
17
|
+
"error": "Payment gateway timeout",
|
|
18
|
+
"exception": "App\\Exceptions\\GatewayTimeout",
|
|
19
|
+
"failed_at": 1749132730000,
|
|
20
|
+
"original_queue": "orders",
|
|
21
|
+
"attempts": 3,
|
|
22
|
+
"lang": "php"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"job": "urn:babel:orders:created",
|
|
3
|
+
"trace_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
|
|
4
|
+
"data": {
|
|
5
|
+
"order_id": 1042
|
|
6
|
+
},
|
|
7
|
+
"meta": {
|
|
8
|
+
"id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
|
|
9
|
+
"queue": "orders",
|
|
10
|
+
"lang": "php",
|
|
11
|
+
"schema_version": 1,
|
|
12
|
+
"created_at": 1749132727000
|
|
13
|
+
},
|
|
14
|
+
"attempts": 0
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Runs the shared cross-SDK conformance suite (vendored under tests/conformance/).
|
|
2
|
+
|
|
3
|
+
The same manifest + fixtures are run by every BabelQueue SDK; passing here proves
|
|
4
|
+
this SDK reads/writes the canonical envelope identically to the others.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import unittest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from babelqueue import EnvelopeCodec
|
|
14
|
+
|
|
15
|
+
SUITE = Path(__file__).parent / "conformance"
|
|
16
|
+
MANIFEST = json.loads((SUITE / "manifest.json").read_text(encoding="utf-8"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConformanceTest(unittest.TestCase):
|
|
20
|
+
def test_suite_is_present(self) -> None:
|
|
21
|
+
self.assertEqual(MANIFEST["schema_version"], 1)
|
|
22
|
+
self.assertGreaterEqual(len(MANIFEST["cases"]), 6)
|
|
23
|
+
|
|
24
|
+
def test_cases(self) -> None:
|
|
25
|
+
for case in MANIFEST["cases"]:
|
|
26
|
+
with self.subTest(case=case["name"]):
|
|
27
|
+
raw = (SUITE / case["file"]).read_text(encoding="utf-8")
|
|
28
|
+
env = EnvelopeCodec.decode(raw)
|
|
29
|
+
self.assertNotEqual(env, {}, "fixture must decode")
|
|
30
|
+
|
|
31
|
+
if not case["valid"]:
|
|
32
|
+
self.assertFalse(
|
|
33
|
+
EnvelopeCodec.accepts(env),
|
|
34
|
+
f"{case['name']} must be rejected: {case.get('reason')}",
|
|
35
|
+
)
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
expect = case["expect"]
|
|
39
|
+
self.assertTrue(EnvelopeCodec.accepts(env), f"{case['name']} must be accepted")
|
|
40
|
+
self.assertEqual(EnvelopeCodec.urn(env), expect["urn"])
|
|
41
|
+
self.assertEqual(env["attempts"], expect["attempts"])
|
|
42
|
+
self.assertEqual(env["meta"]["lang"], expect["lang"])
|
|
43
|
+
self.assertEqual(env["meta"]["schema_version"], expect["schema_version"])
|
|
44
|
+
|
|
45
|
+
if "data" in expect:
|
|
46
|
+
self.assertEqual(env["data"], expect["data"])
|
|
47
|
+
|
|
48
|
+
if "dead_letter" in expect:
|
|
49
|
+
for key, value in expect["dead_letter"].items():
|
|
50
|
+
self.assertEqual(env["dead_letter"][key], value)
|
|
51
|
+
|
|
52
|
+
# Per-message fields must be present (not asserted by value).
|
|
53
|
+
self.assertIn("id", env["meta"])
|
|
54
|
+
self.assertTrue(env["trace_id"])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
unittest.main()
|
|
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
|