Skip to content

Commit 4aee52f

Browse files
committed
Send out-of-int32 ASB application properties as AMQP long
The pure-Python AMQP stack in azure-servicebus encodes a bare Python int as AMQP int32, so publishing failed on the millisecond bq-created-at property. Wrap integer properties outside the int32 range in the stack's explicit long descriptor at the send boundary; the native projection stays plain. Release v1.21.1.
1 parent e00664c commit 4aee52f

5 files changed

Lines changed: 58 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ The envelope wire format is versioned separately by `meta.schema_version`
99

1010
## [Unreleased]
1111

12+
## [1.21.1] - 2026-10-20
13+
14+
### Fixed
15+
- **Azure Service Bus: `publish` no longer fails with `Value supplied for int invalid`.** The pure-Python AMQP
16+
stack in `azure-servicebus` encodes a bare Python int as AMQP int32, so the millisecond `bq-created-at`
17+
application property (broker-bindings.md §4.3) overflowed at send time. Out-of-int32 integer properties
18+
are now sent as AMQP `long`, matching the Go, Java, .NET and Node transports. The native projection and
19+
the envelope body are unchanged.
20+
1221
## [1.21.0] - 2026-10-19
1322

1423
Implements ADR-0032 (conformance packaging, results format, ASCII trim and the non-string `job` rule).

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "babelqueue"
7-
version = "1.21.0"
7+
version = "1.21.1"
88
description = "Polyglot Queues, Simplified — the Python core: the canonical BabelQueue wire-envelope codec, contracts and dead-letter helpers."
99
readme = "README.md"
1010
requires-python = ">=3.9"

‎src/babelqueue/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
Transport,
4242
)
4343

44-
__version__ = "1.21.0"
44+
__version__ = "1.21.1"
4545

4646
__all__ = [
4747
"BabelQueue",

‎src/babelqueue/asb_transport.py‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,36 @@
4444
UNSUPPORTED_SCHEMA_ROUTE_PREFIX = "babelqueue:unsupported-schema-version:"
4545

4646

47+
_INT32_MIN = -(2**31)
48+
_INT32_MAX = 2**31 - 1
49+
50+
51+
def _amqp_typed(props: Dict[str, Any]) -> Dict[str, Any]:
52+
"""Pin out-of-int32 ints to AMQP ``long`` (§4.3: ``bq-created-at`` is Unix ms).
53+
54+
The pure-Python AMQP stack in ``azure-servicebus`` encodes every bare Python int as an
55+
AMQP int32, so a millisecond epoch overflows at send time. A value outside the int32 range
56+
is wrapped in the stack's explicit type descriptor; everything else is left as-is.
57+
"""
58+
try:
59+
from azure.servicebus._pyamqp.types import TYPE, VALUE, AMQPTypes
60+
61+
long_type: Any = AMQPTypes.long
62+
except ImportError: # pragma: no cover - older SDK layouts
63+
TYPE, VALUE, long_type = "TYPE", "VALUE", "LONG"
64+
out: Dict[str, Any] = {}
65+
for key, value in props.items():
66+
if (
67+
isinstance(value, int)
68+
and not isinstance(value, bool)
69+
and not _INT32_MIN <= value <= _INT32_MAX
70+
):
71+
out[key] = {TYPE: long_type, VALUE: value}
72+
else:
73+
out[key] = value
74+
return out
75+
76+
4777
class AsbTransport(Transport):
4878
def __init__(
4979
self,
@@ -196,7 +226,10 @@ def _unsupported_schema_version(properties: Any) -> Optional[str]:
196226
def publish(self, queue: str, body: str) -> None:
197227
from azure.servicebus import ServiceBusMessage
198228

199-
message = ServiceBusMessage(body, **self._projection(body))
229+
kwargs = self._projection(body)
230+
if kwargs.get("application_properties"):
231+
kwargs["application_properties"] = _amqp_typed(kwargs["application_properties"])
232+
message = ServiceBusMessage(body, **kwargs)
200233
self._sender(queue).send_messages(message)
201234

202235
def pop(self, queue: str, timeout: float = 1.0) -> Optional[ReceivedMessage]:

‎tests/test_asb_transport.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,19 @@ def test_publish_builds_message_with_native_projection(self):
322322
self.assertEqual(sent.content_type, "application/json")
323323
self.assertEqual(EnvelopeCodec.urn(EnvelopeCodec.decode(str(sent))), URN)
324324

325+
def test_publish_encodes_ms_created_at_as_amqp_long(self):
326+
from azure.servicebus._pyamqp import _encode
327+
328+
sender = FakeSender()
329+
AsbTransport(client=FakeClient(sender=sender)).publish("orders", body())
330+
331+
props = sender.sent[0].application_properties
332+
out = bytearray()
333+
# a bare int > int32 raises "Value supplied for int invalid" here
334+
_encode.encode_value(out, _encode.encode_application_properties(props))
335+
self.assertIn(b"bq-created-at\x81", bytes(out))
336+
self.assertIn(b"bq-schema-version\x54\x01", bytes(out))
337+
325338

326339
if __name__ == "__main__":
327340
unittest.main()

0 commit comments

Comments
 (0)