feat(python): expose TCP client configuration - #3776
Merged
Conversation
IggyClient accepted only a server address, so auto-login and reconnection tuning were unreachable from Python. Without credentials to replay, the SDK's own session recovery never fires and a dropped session surfaces as Unauthenticated on the next call, leaving the application to hand-roll a connect/login/probe loop. TcpConfig mirrors TcpClientConfig field for field and is accepted by the IggyClient constructor alongside the existing address string. AutoLogin carries the credentials without exposing them back to Python, and TcpReconnectionConfig carries the retry policy. Credentials is re-exported from the SDK prelude because AutoLogin::Enabled cannot be constructed without naming it. Closes apache#3742
Round-trip every field through the getters so a default that drifts from the Rust SDK is caught, and assert that neither the password nor a personal access token comes back out of repr. The auto-login tests are the point of the configuration: a privileged call succeeds without a manual login_user() when credentials are configured, and fails without them.
The existing examples all reach for a connection string, which leaves the new config types undiscoverable. This one configures auto-login and reconnection directly and never calls login_user, so the recovery the credentials unlock is visible: restart the server while it runs and the client picks up where it left off.
The README pointed only at the examples directory, so the configuration surface stayed invisible to anyone reading the package page on PyPI.
A negative timedelta normalizes to negative days plus positive seconds, so the old conversion summed to a negative i32 and cast it to u64, turning interval=timedelta(seconds=-1) into u64::MAX seconds: the config constructed fine and the client then slept forever on reconnect. Days arithmetic also overflowed i32 beyond ~68 years, and the reverse conversion stuffed everything into the seconds argument so such values could not read back. Conversion is now fallible, rejects negative input with ValueError at construction, computes in i64, and splits days on the way out. The AutoCommit conversion becomes TryFrom to carry the error. The boolean constructor defaults were literals in the pyo3 signature, so a change to a Rust default would silently not propagate. They are now Option arguments that fall back to TcpClientConfig::default(), the same way the durations already did.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3776 +/- ##
============================================
- Coverage 76.59% 75.99% -0.60%
Complexity 1046 1046
============================================
Files 1346 1365 +19
Lines 170846 172773 +1927
Branches 142405 142686 +281
============================================
+ Hits 130860 131301 +441
- Misses 36177 37566 +1389
- Partials 3809 3906 +97
🚀 New features to boost your workflow:
|
ethanlin01x
marked this pull request as ready for review
July 29, 2026 18:32
ethanlin01x
marked this pull request as draft
July 29, 2026 18:35
conftest auto-marked every module as integration, so tests explicitly marked unit could not be selected with -m "not integration" even though they need no server. The auto-mark now skips them. New cases pin the duration boundaries (negative rejected, zero legal, beyond the i32 seconds range round-trips) and the README claim that a connection string and TcpConfig reach the same behavior.
The snippet ended with a top-level await; every other sample in the repo wraps in asyncio.run, so paste-and-run failed on the only snippet a PyPI reader sees first.
ethanlin01x
force-pushed
the
feat/python-tcp-config
branch
from
July 29, 2026 18:39
2b6c6ee to
b3190f4
Compare
ethanlin01x
marked this pull request as ready for review
July 29, 2026 19:01
Contributor
Author
|
/ready |
slbotbm
requested changes
Jul 29, 2026
slbotbm
left a comment
Contributor
There was a problem hiding this comment.
Looks good. Mostly cosmetic changes. One thing though: in the docs, you are declaring the thrown errors as PyValueError and similar types. These are rust types which the python user will not see. Also, there are references to the rust sdk in public docs. Please remove them. Our modelled users are python users, who would not know anything about rust.
numinnex
reviewed
Jul 31, 2026
numinnex
reviewed
Jul 31, 2026
numinnex
reviewed
Jul 31, 2026
A separate example for the new config is not needed. The getting-started producer and consumer now build a TcpConfig with auto-login and reconnection instead of a connection string.
The TLS and nodelay options appear as commented-out fields in the snippet instead of prose, and the auto_login and from_connection_string notes are dropped.
Python users see ValueError and RuntimeError rather than the PyO3 exception names, and the wrapped Rust types are an implementation detail.
Docstrings for methods returning Awaitable[None] said they return Ok(()), which does not exist for a Python caller. State the raised exception instead.
IggyDuration::as_micros() truncates the count to u64, so a duration near timedelta.max wrapped to a wrong value instead of surviving the round trip, and the OverflowError guard below could never fire. Read the std Duration directly to keep the u128.
The negative-duration rejection also changed methods that shipped before this branch, such as create_topic's message_expiry, but only the new config classes had coverage.
The tls_validate_certificate docstring was neutral for a flag that accepts any certificate the server presents.
Contributor
Author
Contributor
|
@ethanlin01x you can fix them without creation of issue, just mention that it was found in #3776. |
hubcio
approved these changes
Aug 10, 2026
slbotbm
approved these changes
Aug 10, 2026
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
The six synchronous getters on IggyConsumer took the consumer mutex with blocking_lock() while holding the GIL. consume_messages holds that mutex for the whole consumption run, so reading an attribute during consumption hung the interpreter, and doing it from a callback panicked inside the Tokio runtime. None of those getters need exclusive access: the name, stream and topic are fixed at construction, and the partition id and offsets live behind Arcs in the Rust SDK. IggyConsumerState exposes the latter as a cloneable view, so the Python wrapper can keep its own copies and never touch the lock. Found while reviewing apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
The six synchronous getters on IggyConsumer took the consumer mutex with blocking_lock() while holding the GIL. consume_messages holds that mutex for the whole consumption run, so reading an attribute during consumption hung the interpreter, and doing it from a callback panicked inside the Tokio runtime. None of those getters need exclusive access: the name, stream and topic are fixed at construction, and the partition id and offsets live behind Arcs in the Rust SDK. IggyConsumerState exposes the latter as a cloneable view, so the Python wrapper can keep its own copies and never touch the lock. Found while reviewing apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
The synchronous getters on IggyConsumer took the consumer mutex with blocking_lock() while holding the GIL, and consume_messages holds that mutex for the whole consumption run. Reading an attribute during consumption hung the interpreter; reading one from a callback panicked inside the Tokio runtime. None of those getters need exclusive access. The name, stream and topic are fixed at construction, and the partition id and offsets live behind Arcs that IggyConsumerState now exposes as a cloneable view. IggyConsumer owns that state and delegates to it, so the Python wrapper reads metadata without the lock. Found while reviewing apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
The synchronous getters on IggyConsumer took the consumer mutex with blocking_lock() while holding the GIL, and consume_messages holds that mutex for the whole consumption run. Reading an attribute during consumption hung the interpreter; reading one from a callback panicked inside the Tokio runtime. None of those getters need exclusive access. The name, stream and topic are fixed at construction, and the partition id and offsets live behind Arcs that IggyConsumerState now exposes as a cloneable view. IggyConsumer owns that state and delegates to it, so the Python wrapper reads metadata without the lock. Found while reviewing apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
`init_retry_interval = 0` reached `time::interval`, which asserts on a zero period; the timer is built unconditionally, so it panicked even when the stream and topic already existed, surfacing in bindings as an unnamed panic. `polling_retry_interval = 0` became the poll retry sleep, whose loop body makes no syscall, so it burned a core, and with auto-join disabled the join flag never flips and the spin never ends. `init()` already returns `Result` and must run before polling, so it is the choke point for both, closing the hole for every binding rather than each one guarding its own constructor. Found during review of apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
A zero heartbeat interval pings without pause, and a zero reconnection interval with unlimited retries spins on connect. Found during review of apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
A zero heartbeat interval pings without pause, and a zero reconnection interval with unlimited retries spins on connect. Found during review of apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
A zero heartbeat interval pings without pause, and a zero reconnection interval with unlimited retries spins on connect. Found during review of apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 15, 2026
A zero heartbeat interval pings without pause, and a zero reconnection interval with unlimited retries spins on connect. Found during review of apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 16, 2026
A zero heartbeat interval pings without pause, and a zero reconnection interval with unlimited retries spins on connect. Found during review of apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 20, 2026
The synchronous getters on IggyConsumer took the consumer mutex with blocking_lock() while holding the GIL, and consume_messages holds that mutex for the whole consumption run. Reading an attribute during consumption hung the interpreter; reading one from a callback panicked inside the Tokio runtime. None of those getters need exclusive access. The name, stream and topic are fixed at construction, and the partition id and offsets live behind Arcs that IggyConsumerState now exposes as a cloneable view. IggyConsumer owns that state and delegates to it, so the Python wrapper reads metadata without the lock. Found while reviewing apache#3776.
ethanlin01x
added a commit
to ethanlin01x/iggy
that referenced
this pull request
Aug 20, 2026
The synchronous getters on IggyConsumer took the consumer mutex with blocking_lock() while holding the GIL, and consume_messages holds that mutex for the whole consumption run. Reading an attribute during consumption hung the interpreter; reading one from a callback panicked inside the Tokio runtime. None of those getters need exclusive access. The name, stream and topic are fixed at construction, and the partition id and offsets live behind Arcs that IggyConsumerState now exposes as a cloneable view. IggyConsumer owns that state and delegates to it, so the Python wrapper reads metadata without the lock. Found while reviewing apache#3776.
This was referenced Aug 29, 2026
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR address?
Closes #3742
Rationale
The Python binding accepts only a bare server address, so reconnection and auto-login cannot be configured from Python. The SDK's session recovery is therefore unreachable: a server restart surfaces as
Unauthenticatedon the next call.What changed?
IggyClient(...)took onlyhost:port, withAutoLogin::Disabledhardcoded and the reconnection policy untunable.It now also accepts a keyword-only
TcpConfigmirroring the RustTcpClientConfig(auto_login,reconnection,heartbeat_interval, TLS,nodelay). Unset fields fall back to the Rust defaults, and durations are validateddatetime.timedelta. The bare-address constructor andfrom_connection_stringare unchanged.One behavior change: a negative
timedeltaoncreate_topic/update_topic(message_expiry), theconsumer(...)intervals, orAutoCommit.Interval(...)became a near-u64::MAXduration and now raisesValueError.Local Execution
AI Usage
login_user().