Skip to content

feat(python): add stream listing, update, delete, and purge#3701

Open
felixfaisal wants to merge 1 commit into
apache:masterfrom
felixfaisal:feat/add-python-stream-management-functions
Open

feat(python): add stream listing, update, delete, and purge#3701
felixfaisal wants to merge 1 commit into
apache:masterfrom
felixfaisal:feat/add-python-stream-management-functions

Conversation

@felixfaisal

Copy link
Copy Markdown
Contributor

Which issue does this PR address?

Closes #3520

Rationale

The Python SDK exposed only create_stream and get_stream, so Python users
could not list, rename, delete, or purge streams — operations the Rust SDK
already supports. This closes that parity gap.

What changed?

Added get_streams, update_stream, delete_stream, and purge_stream to the
Python client by wrapping the Rust StreamClient methods via PyO3, plus a new
Stream wrapper class for the list result. Extended test_stream.py with
end-to-end coverage and regenerated apache_iggy.pyi.

Local Execution

  • Passed
  • Pre-commit hooks ran

AI Usage

If AI tools were used, please answer:

  1. Which tools? (e.g., GitHub Copilot, Claude, ChatGPT)
  • Claude Opus (4.8, 1M context)
  1. Scope of usage? (e.g., autocomplete, generated functions, entire implementation)
  • used a skill /teach to understand the bridge between Rust and Python and how the python SDK itself works with iggy, and the changes need to resolve this issue.
  • Once done, reviewed edits to each file manually
  1. How did you verify the generated code works correctly?
  • Reviewed the lines written
  • Created the python stub using this command:
cargo run --bin stub_gen 
  • and then fixed formatting using the below commands:
uv run ruff check --fix apache_iggy.pyi
uv run ruff format apache_iggy.pyi
  • the resulting file looked as expected
  • Ran the iggy server and then the python tests after it, using the commands written below:
  • In one terminal:
cargo run --bin iggy-server -- --with-default-root-credentials --fresh
  • and in another
uv run --no-sync pytest tests/test_stream.py -v
  • all the tests pass
  1. Can you explain every line of the code if asked?
    Yup

Signed-off-by: Faisal Ahmed <faisalahmedfarooq46@gmail.com>
@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 Jul 18, 2026
@felixfaisal

Copy link
Copy Markdown
Contributor Author

/request-review @slbotbm

@felixfaisal

Copy link
Copy Markdown
Contributor Author

/request-review @hubcio

@github-actions
github-actions Bot requested review from hubcio and slbotbm July 18, 2026 23:47
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.63014% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.00%. Comparing base (6f1d548) to head (305d15f).

Files with missing lines Patch % Lines
foreign/python/src/stream.rs 93.75% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3701      +/-   ##
============================================
+ Coverage     73.99%   74.00%   +0.01%     
  Complexity      937      937              
============================================
  Files          1301     1301              
  Lines        147393   147466      +73     
  Branches     122945   122945              
============================================
+ Hits         109063   109138      +75     
+ Misses        34858    34856       -2     
  Partials       3472     3472              
Components Coverage Δ
Rust Core 74.29% <ø> (ø)
Java SDK 62.44% <ø> (ø)
C# SDK 72.18% <ø> (ø)
Python SDK 92.95% <98.63%> (+0.77%) ⬆️
PHP SDK 84.29% <ø> (ø)
Node SDK 91.26% <ø> (ø)
Go SDK 42.87% <ø> (ø)
Files with missing lines Coverage Δ
foreign/python/src/client.rs 99.10% <100.00%> (+0.10%) ⬆️
foreign/python/src/lib.rs 100.00% <100.00%> (ø)
foreign/python/src/stream.rs 94.11% <93.75%> (+16.33%) ⬆️
🚀 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.

@ryankert01 ryankert01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall lg to me, a comment.

Comment on lines +229 to +230
mine = [by_name[name] for name in created]
assert [stream.id for stream in mine] == sorted(stream.id for stream in mine)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs to me that it's not testing get_streams() ordering since it's constructed based on created ordering. Although I'm also not quite sure whether if iggy wants to ensure the ordering or not in the distributed future, kafka for example, don't.


#[gen_stub_pymethods]
#[pymethods]
impl Stream {

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 add getters for created_at and size as well.

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.

Also write their docs as well.

Comment on lines +171 to +172
/// Gets all streams.
/// Returns a list of streams or a PyRuntimeError on failure.

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.

Change to

/// Return all streams visible to the authenticated user.
///
/// The result is ordered by ascending numeric stream ID.
///
/// Returns:
///     A list of `Stream` summaries.
///
/// Raises:
///     RuntimeError: If the client is not authenticated, the user lacks global
///         `read_streams` or `manage_streams` permission, or the request fails.

Comment on lines +184 to +186

/// Updates a stream's name by id.
/// Returns Ok(()) on successful update or a PyRuntimeError on failure.

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.

Change to

/// Rename a stream selected by name or numeric ID.
///
/// `stream_id` accepts a stream name as `str` or numeric ID as `int`. A
/// decimal-only string is interpreted as a numeric ID. `name` must be unique
/// and contain between 1 and 255 UTF-8 bytes. Renaming a stream to its current
/// name succeeds without changing it.
///
/// Returns:
///     None.
///
/// Raises:
///     TypeError: If `stream_id` is neither `str` nor `int`, or `name` is not
///         `str`.
///     OverflowError: If an integer identifier is outside `0..=2**32 - 1`.
///     ValueError: If a string identifier is empty or exceeds 255 UTF-8 bytes.
///     RuntimeError: If the client is not authenticated, the user lacks global
///         `manage_streams` or per-stream `manage_stream` permission, the
///         stream does not exist, the new name is invalid or already used, or
///         the request fails.

Comment on lines +224 to +225
/// Purges all messages from a stream by id.
/// Returns Ok(()) on successful purge or a PyRuntimeError on failure.

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.

Change to

/// Delete all messages from every topic in a stream.
///
/// The stream, topics, and partitions remain available. Repeated purges of an
/// existing empty stream succeed. `stream_id` accepts a stream name as `str`
/// or numeric ID as `int`. A decimal-only string is interpreted as a numeric
/// ID.
///
/// Returns:
///     None.
///
/// Raises:
///     TypeError: If `stream_id` is neither `str` nor `int`.
///     OverflowError: If an integer identifier is outside `0..=2**32 - 1`.
///     ValueError: If a string identifier is empty or exceeds 255 UTF-8 bytes.
///     RuntimeError: If the client is not authenticated, the user lacks global
///         `manage_streams` or per-stream `manage_stream` permission, the
///         stream does not exist, or the request fails.

Comment on lines +205 to +206
/// Deletes a stream by id.
/// Returns Ok(()) on successful deletion or a PyRuntimeError on failure.

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.

Change to

/// Delete a stream selected by name or numeric ID.
///
/// Deletion removes the stream and all of its topics, partitions, and messages.
/// `stream_id` accepts a stream name as `str` or numeric ID as `int`. A
/// decimal-only string is interpreted as a numeric ID.
///
/// Returns:
///     None.
///
/// Raises:
///     TypeError: If `stream_id` is neither `str` nor `int`.
///     OverflowError: If an integer identifier is outside `0..=2**32 - 1`.
///     ValueError: If a string identifier is empty or exceeds 255 UTF-8 bytes.
///     RuntimeError: If the client is not authenticated, the user lacks global
///         `manage_streams` or per-stream `manage_stream` permission, the
///         stream does not exist, or the request fails.

}

#[getter]
pub fn topics_count(&self) -> u32 {

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.

Doc: /// Number of topics in the stream.

Comment on lines +267 to +277
async def test_get_streams_before_connect_fails(self):
"""Test get_streams requires an established connection."""
host, port = get_server_config()
client = IggyClient(f"{host}:{port}")

with pytest.raises(RuntimeError):
await client.get_streams()

@pytest.mark.asyncio
async def test_get_streams_before_login_fails(self):
"""Test get_streams requires authentication."""

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.

Make these into 1 test

Comment on lines +306 to +311

old = await iggy_client.get_stream(stream_name)
assert old is None

@pytest.mark.asyncio
async def test_update_stream_preserves_id(

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.

These two tests can be combined into one. No need for another test just to assert stream id remains same.

Comment on lines +388 to +389
with pytest.raises(RuntimeError):
await iggy_client.update_stream(stream_id=second_stream, name=first_stream)

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.

After the failure, assert that none of the metadata of the stream has changed.

await client.get_streams()


class TestUpdateStream:

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.

Add the following tests for update_stream:

  • it accepts exact 1-byte and 255-byte names and rejects empty and 256-byte names, including UTF-8 byte boundaries.

@slbotbm

slbotbm commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Please do not force push. Instead, create new commits with your work. This makes it easier for me to understand the changes

/author

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

[python sdk] Add functions related to stream management

3 participants