This is an automated email from the ASF dual-hosted git repository.

spetz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new e0a8196ef feat(python): expose update_user options (#4173)
e0a8196ef is described below

commit e0a8196efd7238a98ab9f4228ac8fb2455b5f408
Author: Jorge Polanco <[email protected]>
AuthorDate: Mon Sep 14 07:41:14 2026 -0600

    feat(python): expose update_user options (#4173)
    
    Closes #4164.
    
    ---------
    
    Co-authored-by: Piotr Gankiewicz <[email protected]>
---
 foreign/python/apache_iggy.pyi    |  4 ++++
 foreign/python/src/client.rs      | 20 +++++++++++---------
 foreign/python/tests/test_user.py | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 51 insertions(+), 9 deletions(-)

diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi
index e628a7c4d..579fca81c 100644
--- a/foreign/python/apache_iggy.pyi
+++ b/foreign/python/apache_iggy.pyi
@@ -1246,6 +1246,7 @@ class IggyClient:
         user_id: builtins.str | builtins.int,
         username: builtins.str | None = None,
         status: UserStatus | None = None,
+        options: builtins.dict[builtins.str, builtins.str] | None = None,
     ) -> collections.abc.Awaitable[None]:
         r"""
         Update a user by unique ID or username.
@@ -1254,6 +1255,9 @@ class IggyClient:
             user_id: User identifier as `str | int`.
             username: New username as `str | None`; unchanged when `None`.
             status: New status as `UserStatus | None`; unchanged when `None`.
+            options: Reserved for future use. Additional option keys as
+                `dict[str, str] | None`, forwarded to the server. No user 
update
+                option key exists yet, so a current server rejects every key.
 
         Returns:
             An awaitable that resolves to `None` when the user is updated.
diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs
index d75f7c350..4c5799c17 100644
--- a/foreign/python/src/client.rs
+++ b/foreign/python/src/client.rs
@@ -354,6 +354,9 @@ impl IggyClient {
     ///     user_id: User identifier as `str | int`.
     ///     username: New username as `str | None`; unchanged when `None`.
     ///     status: New status as `UserStatus | None`; unchanged when `None`.
+    ///     options: Reserved for future use. Additional option keys as
+    ///         `dict[str, str] | None`, forwarded to the server. No user 
update
+    ///         option key exists yet, so a current server rejects every key.
     ///
     /// Returns:
     ///     An awaitable that resolves to `None` when the user is updated.
@@ -361,7 +364,7 @@ impl IggyClient {
     /// Raises:
     ///     ValueError: If a string identifier is invalid.
     ///     RuntimeError: If the request fails.
-    #[pyo3(signature = (user_id, username=None, status=None))]
+    #[pyo3(signature = (user_id, username=None, status=None, options=None))]
     
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]", 
imports=("collections.abc")))]
     fn update_user<'a>(
         &self,
@@ -369,20 +372,19 @@ impl IggyClient {
         user_id: PyIdentifier,
         #[gen_stub(override_type(type_repr = "builtins.str | None"))] 
username: Option<String>,
         #[gen_stub(override_type(type_repr = "UserStatus | None"))] status: 
Option<PyUserStatus>,
+        #[gen_stub(override_type(type_repr = "builtins.dict[builtins.str, 
builtins.str] | None"))]
+        options: Option<BTreeMap<String, String>>,
     ) -> PyResult<Bound<'a, PyAny>> {
         let user_id = Identifier::try_from(user_id)?;
         let status = status.map(UserStatus::from);
+        let update_options = UserUpdateOptions {
+            raw: options.unwrap_or_default(),
+        };
         let inner = self.inner.clone();
 
         future_into_py(py, async move {
             inner
-                .update_user(
-                    &user_id,
-                    username.as_deref(),
-                    status,
-                    // Users have no option keys yet.
-                    &UserUpdateOptions::default(),
-                )
+                .update_user(&user_id, username.as_deref(), status, 
&update_options)
                 .await
                 .map_err(to_runtime_error)?;
             Ok(())
@@ -590,7 +592,7 @@ impl IggyClient {
     ///         `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.
-    #[pyo3(signature = (stream_id, name, options = None))]
+    #[pyo3(signature = (stream_id, name, options=None))]
     
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]", 
imports=("collections.abc")))]
     fn update_stream<'a>(
         &self,
diff --git a/foreign/python/tests/test_user.py 
b/foreign/python/tests/test_user.py
index a15b714b1..f6a328ca1 100644
--- a/foreign/python/tests/test_user.py
+++ b/foreign/python/tests/test_user.py
@@ -361,6 +361,42 @@ class TestUpdateUser:
 
         await iggy_client.delete_user(created.id)
 
+    @pytest.mark.asyncio
+    async def test_update_user_with_empty_options_succeeds(
+        self, iggy_client: IggyClient, unique_name
+    ):
+        """Test update_user accepts an empty options map."""
+        username, password = unique_credentials(unique_name)
+        new_username = unique_name(max_bytes=MAX_USERNAME_BYTES)
+        created = await iggy_client.create_user(username, password)
+
+        await iggy_client.update_user(created.id, username=new_username, 
options={})
+
+        user = await iggy_client.get_user(created.id)
+        assert user is not None
+        assert user.username == new_username
+
+        await iggy_client.delete_user(created.id)
+
+    @pytest.mark.asyncio
+    async def test_update_user_forwards_options(
+        self, iggy_client: IggyClient, unique_name
+    ):
+        """Test update_user forwards option keys to the server."""
+        username, password = unique_credentials(unique_name)
+        created = await iggy_client.create_user(username, password)
+
+        with pytest.raises(RuntimeError) as rejection:
+            await iggy_client.update_user(
+                created.id,
+                options={"unknown": "value"},
+            )
+
+        # Binary transports carry the code alone, so the key itself is empty.
+        assert "Unsupported option key" in str(rejection.value)
+
+        await iggy_client.delete_user(created.id)
+
     @pytest.mark.asyncio
     async def test_update_user_with_no_fields_is_a_noop(
         self, iggy_client: IggyClient, unique_name

Reply via email to