feat(python): add user header and origin timestamp support - #3613
Conversation
|
one additional request from my side: please update python examples to include a code that would show how to use iggy message user headers. there is one for rust already. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3613 +/- ##
============================================
- Coverage 75.86% 75.81% -0.06%
Complexity 969 969
============================================
Files 1323 1324 +1
Lines 160111 160623 +512
Branches 133497 133576 +79
============================================
+ Hits 121468 121770 +302
- Misses 35001 35122 +121
- Partials 3642 3731 +89
🚀 New features to boost your workflow:
|
That will be included when the PR is ready. |
a5a6357 to
6d29d49
Compare
|
I was thinking that maybe we should change the signature of the headers from This would allow us to have two advantages: users that do not have much experience/do not require explicit control can use What do you guys think? |
I think it's a good idea and well aligned with the first point worth discussing in my PR. I'll push a new patch soon. |
|
@slbotbm for me this sounds fine, however one thing comes to my mind: will it have any impact on performance? |
Actually, I think additional typed The only overhead we introduce is the iteration of |
|
alright, then don't worry about it now. |
0fdd454 to
8daf546
Compare
|
Hi, I just reflected the change and merged all the commits into a final one. The PR message is also updated. Instead of a plain-only Please have a look at it when you are free. |
8daf546 to
90b153d
Compare
|
/author |
90b153d to
209ecd7
Compare
|
/ready |
slbotbm
left a comment
There was a problem hiding this comment.
Some more comments:
- You have set the type of user headers in some places to [Any, Any], which makes any type checker incapable of catching mistakes. Would be better to explicitly declare the types.
209ecd7 to
5710bff
Compare
|
/ready |
Add user headers to SendMessage and ReceiveMessage, expose origin timestamp, and fix Docker test infrastructure: - explicit typed header and plain Python header support - type transformation and validation across Rust and Python - user header usage examples - minor fix: Docker test config file
to_scalar_dict Split message-headers example into plain-headers and typed-headers variants sharing logic via common.py. Rename UserHeaders.to_plain() to to_scalar_dict() to better describe the returned dict type.
examples to avoid infinitly produce_message loop.
0164a80 to
66d9191
Compare
|
/ready |
reject inf float header in sending and receiving path.
StrEnum is not available in Python 3.10 and older 3.11 releases
66d9191 to
a69af8f
Compare
|
@jiengup Could you resolve the conflicts? Other than that LGTM |
|
/author |
|
/ready |
Add user headers to SendMessage and ReceiveMessage, expose origin timestamp, and fix Docker test infrastructure.
Which issue does this PR address?
Closes #3601 #3612
Rationale
The Python SDK could send and receive message payloads, but it did not expose the typed user headers already carried by the underlying Rust
IggyMessage. This left Python behind the Rust, Node, Go, Java, and C# SDKs for message metadata.What changed?
Python messages can attach user headers and read them back from received messages.
SendMessageacceptsuser_headersand an optional customid, whileReceiveMessageexposesuser_headers()andorigin_timestamp().According to the discussion below, unlike the original proposal in #3601 (which exposed only a plain
dict[str, str | bytes | bool | int | float]), the binding now supports the full Rust header type surface through two exposed classes, plus a plain-scalar convenience layer:HeaderKey/HeaderValue— typed complex enums covering everyHeaderKind:Raw,String,Bool,Int8/16/32/64/128,UnsignedInt8/16/32/64/128,Float32,Float64.UserHeaders— the mapping returned byReceiveMessage.user_headers(). It is a realdictsubclass (dict[HeaderKey, HeaderValue]), so all mapping operations work, and it adds a chainableto_scalar_dict()for the convenient scalar form:message.user_headers().to_scalar_dict().Sending (
plain → Rust)Each key/value pair is converted independently, so typed and plain forms can be freely mixed in one dict (e.g.
{HeaderKey.String("a"): 7, "b": HeaderValue.Bool(True), "c": "plain"}):str):str → String,bytes → Raw,bool → Boolint →the smallest header kind that holds it exactly: non-negative →Uint8/16/32/64/128, negative →Int8/16/32/64/128; values beyond the 128-bit range raiseValueError.float → Float32when the value is exactly representable as f32, otherwiseFloat64(always lossless).OverflowErrorfor out-of-range, e.g.HeaderValue.Int8(300)), and an explicitFloat32whose value overflows f32 (→inf) is rejected withValueError.Receiving (
Rust → plain)Every
HeaderKindmaps losslessly onto a Python scalar (128-bit ints fit Python's arbitrary-precisionint;f32 → f64is exact), so:user_headers()returns typedUserHeaders(dict[HeaderKey, HeaderValue]), orNonewhen the message carries no user headers.UserHeaders.to_scalar_dict()returnsdict[str | bytes | bool | int | float, str | bytes | bool | int | float]— a direct, lossless conversion (no logging); it only raisesValueErroron a genuine decode error of a stored field.Header decode errors from known-but-invalid or unknown semantic kinds surface as
ValueError.SendMessage(id=...)(mapped tou128) is part of the same binding update.Minor Fix
The Python test compose setup starts the server with fresh default root credentials, binds HTTP/TCP/QUIC addresses explicitly, and uses
iggy pingfor the healthcheck instead of the HTTP stats endpoint.Discussion Notes
Choices from the issue discussion, updated to the final implementation:
dictAPI for the common case and explicitHeaderKey/HeaderValuewrapper classes; the two can be mixed per entry.ValueError.Float32/Float64by exact representability instead of alwaysFloat64.HeaderKey), instead of being rejected.ReceiveMessage.user_headers()returnsNonefor no headers.origin_timestamp()is exposed on received messages.API Usage
Sending — plain scalars (common case)
Sending — explicit typed kinds
Sending — mixed typed + plain in one dict
Receiving
Validation / errors
Local Execution
cargo fmt --check --manifest-path foreign/python/Cargo.tomlcargo check --manifest-path foreign/python/Cargo.tomlcargo test --manifest-path foreign/python/Cargo.tomluv run --extra dev ruff check tests/test_message_operations.py tests/test_consumer_group.py.venv/bin/python -m pytest tests/test_message_operations.py::TestMessageOperations::test_invalid_user_headers_are_rejected -qexample/message-headers/typed-headers/consumer.py,example/message-headers/typed-headers/producer.py,example/message-headers/plain-headers/consumer.py,example/message-headers/plain-headers/producer.pyAI Usage
Codex was used to inspect the existing Python, Rust, Node, and Go SDK behavior, implement the Python binding changes, add tests. All the modification was reviewed carefully by the human.