Skip to content

feat(python): expose remaining Topic/TopicDetails fields and partitions#3623

Open
mattp5657 wants to merge 8 commits into
apache:masterfrom
mattp5657:feat/python-sdk-topic-details-fields
Open

feat(python): expose remaining Topic/TopicDetails fields and partitions#3623
mattp5657 wants to merge 8 commits into
apache:masterfrom
mattp5657:feat/python-sdk-topic-details-fields

Conversation

@mattp5657

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Closes #3577

Rationale

Continuation of #3572. That PR exposed Topic/TopicDetails basics but left several fields returned by the server unavailable in Python, so callers couldn't inspect a topic's expiry, size, max size, or its partitions without another SDK.

What changed?

Topic and TopicDetails previously exposed only id, name, partitions_count, compression_algorithm, and replication_factor, leaving created_at, size, message_expiry, max_topic_size, and per-partition data unavailable in Python.

Both classes now expose created_at, size, message_expiry (new IggyExpiry enum), and max_topic_size (new MaxTopicSize enum). TopicDetails also gains partitions, a list of new Partition objects. Partition (core/common) now derives Clone to support building that list.

Local Execution

  • Passed
  • Pre-commit hooks ran

AI Usage

Claude was used to help generate and review this PR.

@mattp5657
mattp5657 marked this pull request as ready for review July 7, 2026 00:01
@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Jul 7, 2026
@mattp5657 mattp5657 changed the title Feat/python sdk topic details fields feat(python): expose remaining Topic/TopicDetails fields and partitions Jul 7, 2026
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.22981% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.43%. Comparing base (354be5c) to head (a511223).
⚠️ Report is 41 commits behind head on master.

Files with missing lines Patch % Lines
foreign/python/src/topic.rs 78.18% 24 Missing ⚠️
foreign/python/src/consumer.rs 87.50% 2 Missing ⚠️
foreign/python/src/client.rs 96.87% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3623      +/-   ##
============================================
- Coverage     74.11%   71.43%   -2.68%     
  Complexity      937      937              
============================================
  Files          1262     1286      +24     
  Lines        132757   136080    +3323     
  Branches     108647   111880    +3233     
============================================
- Hits          98396    97213    -1183     
- Misses        31273    35360    +4087     
- Partials       3088     3507     +419     
Components Coverage Δ
Rust Core 71.24% <ø> (-3.28%) ⬇️
Java SDK 62.44% <ø> (ø)
C# SDK 71.40% <ø> (-0.71%) ⬇️
Python SDK 90.36% <83.22%> (-1.30%) ⬇️
PHP SDK 84.29% <ø> (ø)
Node SDK 91.35% <ø> (ø)
Go SDK 42.28% <ø> (ø)
Files with missing lines Coverage Δ
foreign/python/src/lib.rs 100.00% <100.00%> (ø)
foreign/python/src/client.rs 98.90% <96.87%> (+0.02%) ⬆️
foreign/python/src/consumer.rs 81.11% <87.50%> (+0.57%) ⬆️
foreign/python/src/topic.rs 82.66% <78.18%> (-12.34%) ⬇️

... and 239 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.

@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.

You've defined MaxTopicSize and IggyExpiry as enums here, but the sdk utilizes normal python ints and timedeltas. Let's change those functions to accept your enums instead of the python ints and structs.

@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 Jul 8, 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.

will review in more depth later when I get more time

Comment thread foreign/python/src/topic.rs Outdated
Comment on lines +32 to +37
/// Use the server's default message expiry.
ServerDefault(),
/// Messages expire after this duration.
ExpireDuration { duration: Py<PyDelta> },
/// Messages never expire.
NeverExpire(),

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.

Please also write exactly what does server default and never expire mean. Also, range of values that expireduration accepts

Comment on lines +71 to +78
pub enum MaxTopicSize {
/// Use the server's default max size.
ServerDefault(),
/// The topic is limited to this many bytes.
Custom { bytes: u64 },
/// The topic has no maximum size.
Unlimited(),
}

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.

Same here. Make the docs explicit. We are doing this since the user cannot trace what exactly these mean without looking at rust sdk code.

@mattp5657 mattp5657 closed this Jul 9, 2026
@github-actions github-actions Bot removed the S-waiting-on-author PR is waiting on author response label Jul 9, 2026
@mattp5657 mattp5657 reopened this Jul 9, 2026
@slbotbm

slbotbm commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

/author

@github-actions github-actions Bot added the S-waiting-on-author PR is waiting on author response label Jul 9, 2026
@mattp5657

Copy link
Copy Markdown
Contributor Author

You've defined MaxTopicSize and IggyExpiry as enums here, but the sdk utilizes normal python ints and timedeltas. Let's change those functions to accept your enums instead of the python ints and structs.

This was an oversight from me, thanks for the call out :)

@mattp5657

Copy link
Copy Markdown
Contributor Author

I made the outlined changes, my only concern is if we should continue with backwards compatibility and incorporate it into testing. But wanted to get your thoughts before doing so. :)

@mattp5657

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Jul 10, 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.

Some comments

Comment thread foreign/python/src/topic.rs Outdated
match expiry {
IggyExpiry::ServerDefault() => RustIggyExpiry::ServerDefault,
IggyExpiry::ExpireDuration { duration } => {
RustIggyExpiry::ExpireDuration(py_delta_to_iggy_duration(duration))

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 also accepts negative duration, which should be rejected as PyValueError

Comment thread foreign/python/src/topic.rs Outdated
/// the server deletes the oldest sealed segments to make room for new
/// messages.
///
/// `bytes` must be greater than zero and less than `u64::MAX`: those two

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.

Python callers do not know what u64::MAX means.

@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 Jul 12, 2026
@mattp5657

Copy link
Copy Markdown
Contributor Author

Fixed both boundary-collision issues flagged in review: negative timedelta values for IggyExpiry.ExpireDuration and 0/u64::MAX values for MaxTopicSize.Custom were silently accepted and misinterpreted on the wire (as ServerDefault/Unlimited) instead of being rejected. Both conversions now raise PyValueError for out-of-range input, propagated through create_topic/update_topic. Updated the affected doc comments/stubs and added a regression test for each invalid case.

@mattp5657

Copy link
Copy Markdown
Contributor Author

/ready

@github-actions github-actions Bot added S-waiting-on-review PR is waiting on a reviewer and removed S-waiting-on-author PR is waiting on author response labels Jul 12, 2026
Python::attach(|py| {
let delta = delta1.bind(py);
let seconds = (delta.get_days() * 60 * 60 * 24 + delta.get_seconds()) as u64;
let total_seconds = delta.get_days() * 60 * 60 * 24 + delta.get_seconds();

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.

delta.get_days() * 60 * 60 * 24 + delta.get_seconds() is all i32 here (pyo3's get_days/get_seconds both return i32). for |days| >= 24856 (~68yr) the multiply overflows - release wheels wrap silently, debug builds panic. the nasty part is it defeats the negative-duration guard this PR just added right below: a large negative timedelta (easy to get from date arithmetic) wraps to a positive total_seconds, slips past the if total_seconds < 0 check, and a positive expiry gets persisted from a negative input. one-line fix: let total_seconds = i64::from(delta.get_days()) * 86_400 + i64::from(delta.get_seconds()); then range-check before the as u64.

#[gen_stub(override_type(type_repr = "datetime.timedelta | None", imports=("datetime")))]
message_expiry: Option<Py<PyDelta>>,
#[gen_stub(override_type(type_repr = "builtins.int | None"))] max_topic_size: Option<u64>,
#[gen_stub(override_type(type_repr = "IggyExpiry | None"))] message_expiry: Option<

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.

note this is a breaking change for create_topic.message_expiry - it went from timedelta | None to IggyExpiry | None, and that param shipped in the released 0.8.0, so existing callers passing a timedelta now hit a TypeError. the max_topic_size retype and all of update_topic are post-0.8.0 so those break nothing released. worth calling out the message_expiry break, at least in the PR descr.

@mattp5657 mattp5657 Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I left a comment but hadn't updated the PR description yet. I know it's sometimes easier to follow a PR when the description reflects the original push and comments show how the discussion evolved, but I should have been clearer here, apologies.

I see three options:

  1. Keep the current change. I think it's the most robust approach, but I understand it's a breaking change for message_expiry.
  2. Revert to the original types used to init the server settings (backwards compatible).
  3. Accept both, support the old type alongside the new enum.

My main concern is whether we should preserve backwards compatibility here and add test coverage for it, but I wanted to get your thoughts before going down that path.

/// such by the server. A negative `timedelta` raises `ValueError` when
/// this value is passed to `create_topic`/`update_topic`. The upper
/// bound is whatever a `datetime.timedelta` can represent that also fits
/// in a `u64` microsecond count (about 584,942 years); in practice the

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 bound isn't accurate today - the timedelta conversion in py_delta_to_iggy_duration overflows i32 at ~68 years, so the real ceiling is far below what's documented. the figure is also a typo: u64 microseconds is ~584,542 years, not 584,942. once the overflow is widened to i64 the ~584,542y bound becomes real and this doc is correct.

Ok(match max_size {
MaxTopicSize::ServerDefault() => RustMaxTopicSize::ServerDefault,
MaxTopicSize::Custom { bytes } => {
if *bytes == 0 || *bytes == u64::MAX {

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.

small consistency thing: MaxTopicSize.Custom(0) (and u64::MAX) reject with ValueError here, but the sibling sentinel IggyExpiry.ExpireDuration(timedelta(0)) silently coerces to server-default instead of rejecting. both are documented so it's not a bug, just worth picking one policy across the two - either both reject the reserved value or both coerce it.


/// The collection of partitions in the topic.
#[getter]
pub fn partitions(&self) -> Vec<Partition> {

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 getter clones the whole partition vec and builds fresh python objects on every access, so topic.partitions looks like a cheap attribute but re-materializes each time (and the returned objects don't have stable identity). it's a cold metadata path so not a perf concern, but worth a doc note or exposing it as a method so callers don't read it repeatedly in a loop.

@@ -169,7 +170,9 @@ impl IggyClient {
}

/// Creates a new topic with the given parameters.

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.

minor: create_topic documents its return and raises as free prose while update_topic below uses the structured Args/Returns/Raises form. worth aligning the two.

Some(delta) => IggyExpiry::ExpireDuration(py_delta_to_iggy_duration(&delta)),
None => IggyExpiry::ServerDefault,
};
let expiry = message_expiry

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 expiry + max_size resolution block is duplicated byte-for-byte in update_topic. could pull it into a small helper (or two) to dedup, no behavior change.

("max_topic_size_bytes", "expected_exception"),
[
(-1, OverflowError),
(2e64, TypeError),

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 create_topic version of this test also covers (0, ValueError) and (2**64-1, ValueError) for the reserved sentinels, but they're missing here even though update goes through the same try_from. there's also no update_topic test for a negative ExpireDuration like create has. worth mirroring the create cases.

@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 Jul 15, 2026
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.

Expose remaining Topic and TopicDetails fields in the Python SDK

3 participants