Skip to content

feat(python): add QuicConfig transport configuration - #3991

Open
saie-ch wants to merge 2 commits into
apache:masterfrom
saie-ch:python-quic-config
Open

feat(python): add QuicConfig transport configuration#3991
saie-ch wants to merge 2 commits into
apache:masterfrom
saie-ch:python-quic-config

Conversation

@saie-ch

@saie-ch saie-ch commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Relates to #2835.

Rationale

Python's QUIC transport was only reachable through the untested from_connection_string() path, with no config object like TCP got in #3776.

What changed?

Added QuicConfig/QuicReconnectionConfig, accepted by IggyClient.quic(...), mirroring the TcpConfig pattern. Also fixed a bug where from_connection_string() failed on iggy+quic:// URLs because quinn::Endpoint::client needs an active Tokio runtime context.

Local Execution

  • All related tests passed
  • Pre-commit hooks ran

@github-actions

Copy link
Copy Markdown

Thanks for the PR. It is labeled S-waiting-on-review and queued for review.

Slash commands (own line, regular comment) move it around the queue:

  • /ready - back to S-waiting-on-review after addressing feedback
  • /author - flip to S-waiting-on-author while you finish changes
  • /request-review @user-or-team - request a reviewer

See CONTRIBUTING.md for details.

@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Aug 29, 2026
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.15498% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.92%. Comparing base (5916e6f) to head (b56981f).

Files with missing lines Patch % Lines
foreign/python/src/config.rs 97.93% 5 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3991      +/-   ##
============================================
+ Coverage     84.91%   84.92%   +0.01%     
  Complexity     1405     1405              
============================================
  Files          1224     1224              
  Lines        179301   179572     +271     
  Branches     145615   145614       -1     
============================================
+ Hits         152250   152501     +251     
+ Misses        23024    23020       -4     
- Partials       4027     4051      +24     
Components Coverage Δ
Rust Core 85.79% <ø> (-0.02%) ⬇️
Java SDK 67.35% <ø> (ø)
C# SDK 75.38% <ø> (-0.02%) ⬇️
Python SDK 90.91% <98.15%> (+0.85%) ⬆️
PHP SDK 85.65% <ø> (ø)
Node SDK 96.16% <ø> (+0.02%) ⬆️
Go SDK 69.29% <ø> (+0.03%) ⬆️
Files with missing lines Coverage Δ
foreign/python/src/client.rs 99.86% <100.00%> (+<0.01%) ⬆️
foreign/python/src/duration.rs 97.50% <100.00%> (+1.50%) ⬆️
foreign/python/src/lib.rs 100.00% <100.00%> (ø)
foreign/python/src/config.rs 97.52% <97.93%> (+0.49%) ⬆️

... and 37 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread foreign/python/src/duration.rs Outdated
/// Converts a Python timedelta to milliseconds, for fields the Rust SDK
/// stores as a raw millisecond count rather than an `IggyDuration` (e.g.
/// QUIC's `keep_alive_interval`/`max_idle_timeout`).
pub fn py_delta_to_millis(delta: &Py<PyDelta>) -> PyResult<u64> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as_millis() truncates, and 0 is a magic value in configure():

QuicConfig(keep_alive_interval=timedelta(microseconds=500))  # -> 0 -> keep-alive off
QuicConfig(max_idle_timeout=timedelta(microseconds=500))     # -> 0 -> quinn's 30 s default

Please raise ValueError when a non-zero duration rounds down to 0 ms.

/// receive_window: Receive window size in bytes. Defaults to 100,000.
/// keep_alive_interval: Interval between QUIC keep-alive pings, or a zero
/// duration to disable them. Defaults to 5 seconds.
/// max_idle_timeout: How long the connection tolerates silence before it is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The max_idle_timeout docs say a zero duration means "no limit". It actually means "quinn's 30 s default", because configure() skips the setter entirely.

Comment thread foreign/python/src/config.rs Outdated
}
if let Some(max_concurrent_bidi_streams) = max_concurrent_bidi_streams {
inner.max_concurrent_bidi_streams =
u64_param(max_concurrent_bidi_streams, "max_concurrent_bidi_streams")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_concurrent_bidi_streams and receive_window are validated as u64, but they go through VarInt::try_from (max 2^62 - 1).

Comment thread foreign/python/src/config.rs Outdated
u64_param(datagram_send_buffer_size, "datagram_send_buffer_size")?;
}
if let Some(initial_mtu) = initial_mtu {
inner.initial_mtu = u16_param(initial_mtu, "initial_mtu")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quinn clamps this: TransportConfig::initial_mtu does value.max(1200). So QuicConfig(initial_mtu=500).initial_mtu reads back 500 while the connection runs at 1200. Please reject anything below 1200.

auto_login=AutoLogin.username_password("iggy", "iggy"),
)
)
await client.connect()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and test_without_auto_login_a_privileged_call_is_unauthenticated use the default unlimited reconnection, so a missing QUIC listener hangs until the CI timeout instead of failing.

test_wrong_auto_login_credentials_fail below already passes QuicReconnectionConfig(enabled=False). Same here?

Reject durations that round down to 0ms, fix the max_idle_timeout
docstring, validate max_concurrent_bidi_streams/receive_window against
VarInt::MAX, reject initial_mtu below quinn's 1200 floor, and disable
reconnection in the two auto-login tests that could otherwise hang.
@saie-ch
saie-ch force-pushed the python-quic-config branch from 52d0f86 to b56981f Compare August 31, 2026 05:47
@saie-ch

saie-ch commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @ethanlin01x Could you check now i've addressed the following

  • duration.rs: non-zero durations that round down to 0ms now raise ValueError instead of silently becoming the zero magic value.
  • Corrected the max_idle_timeout docstring to say it falls back to quinn's 30s default, not "no limit".
  • max_concurrent_bidi_streams/receive_window now validate against VarInt::MAX (2^62 - 1), not just u64::MAX.
  • initial_mtu now rejects anything below 1200 instead of accepting a value quinn silently clamps up.
  • Same here — added QuicReconnectionConfig(enabled=False) to both tests.

Comment thread foreign/python/README.md
Comment on lines +169 to +200
asyncio.run(main())
```

`IggyClient.quic(...)` takes a `QuicConfig` the same way, built from `IggyClient.quic()`'s own
config type rather than passed to `IggyClient(...)`:

```python
import asyncio
from datetime import timedelta

from apache_iggy import AutoLogin, IggyClient, QuicConfig, QuicReconnectionConfig


async def main():
client = IggyClient.quic(
QuicConfig(
server_address="127.0.0.1:8080",
server_name="localhost",
auto_login=AutoLogin.username_password("iggy", "iggy"),
reconnection=QuicReconnectionConfig(
enabled=True,
max_retries=10,
interval=timedelta(seconds=2),
reestablish_after=timedelta(seconds=30),
),
heartbeat_interval=timedelta(seconds=5),
# validate_certificate=True,
)
)
await client.connect()


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think we need that much code in main README.md for python, remove it

@github-actions github-actions Bot added S-waiting-on-author PR is waiting on author response and removed S-waiting-on-review PR is waiting on a reviewer labels Aug 31, 2026

@slbotbm slbotbm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's fold the config example code into existing examples as comments, and expose IggyClient (... | QuicConfig | ... ) instead of IggyClient.quic method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author PR is waiting on author response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants