Skip to content

feat(python): add user management methods to IggyClient#3695

Open
ethanlin01x wants to merge 8 commits into
apache:masterfrom
ethanlin01x:feat/python-sdk-user-management
Open

feat(python): add user management methods to IggyClient#3695
ethanlin01x wants to merge 8 commits into
apache:masterfrom
ethanlin01x:feat/python-sdk-user-management

Conversation

@ethanlin01x

@ethanlin01x ethanlin01x commented Jul 17, 2026

Copy link
Copy Markdown

Which issue does this PR address?

Closes #3682

Rationale

Adds the missing user management operations to the Python SDK, which previously had no binding beyond login_user and forced callers to the CLI or another SDK to provision users.

What changed?

The Python SDK exposed no part of the Rust UserClient surface besides login_user, so users could not be listed, inspected, created, updated, or deleted from Python.

get_user, get_users, create_user, update_user, and delete_user now bind through to the Rust UserClient, and UserInfo, UserInfoDetails, and UserStatus are exposed as Python classes. As scoped in the issue, create_user always passes None for permissions; the Permissions mapping is left to a follow-up together with update_permissions, change_password, and logout_user.

Local Execution

  • Passed
  • Pre-commit hooks ran

AI Usage

Claude was used to help generate and review this PR and all the changes are checked by the human.

@ethanlin01x
ethanlin01x force-pushed the feat/python-sdk-user-management branch from 321886e to 6fec2bc Compare July 17, 2026 15:44
The Python bindings need these types to expose user management, but
only UserStatus was re-exported so far.
These types are needed by the upcoming user management methods on
IggyClient. Permissions stay unexposed for now, so UserInfoDetails
carries the same fields as UserInfo until the follow-up that maps
the Permissions structure.
Expose get_user, get_users, create_user, update_user and delete_user,
wrapping the Rust SDK UserClient functions like the existing topic and
consumer group bindings. create_user always passes no permissions; the
Permissions type mapping is left to a follow-up together with
update_permissions, change_password and logout_user.
Cover create, get, list, update and delete against a live server,
including default status, inactive users, login with created
credentials, numeric and name identifiers, repeated listing stability,
and pre-connection and pre-authentication failures.
@ethanlin01x
ethanlin01x force-pushed the feat/python-sdk-user-management branch from 6fec2bc to 7e57428 Compare July 17, 2026 15:51
@ethanlin01x
ethanlin01x marked this pull request as ready for review July 17, 2026 15:53
@github-actions github-actions Bot added the S-waiting-on-review PR is waiting on a reviewer label Jul 17, 2026
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.34513% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.11%. Comparing base (6f1d548) to head (7e57428).

Files with missing lines Patch % Lines
foreign/python/src/user.rs 93.02% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3695      +/-   ##
============================================
- Coverage     73.99%   65.11%   -8.88%     
  Complexity      937      937              
============================================
  Files          1301     1301              
  Lines        147393   132516   -14877     
  Branches     122945   108032   -14913     
============================================
- Hits         109063    86292   -22771     
- Misses        34858    42722    +7864     
- Partials       3472     3502      +30     
Components Coverage Δ
Rust Core 63.37% <ø> (-10.92%) ⬇️
Java SDK 62.44% <ø> (ø)
C# SDK 71.04% <ø> (-1.14%) ⬇️
Python SDK 92.73% <97.34%> (+0.56%) ⬆️
PHP SDK 84.29% <ø> (ø)
Node SDK 91.35% <ø> (+0.09%) ⬆️
Go SDK 42.87% <ø> (ø)
Files with missing lines Coverage Δ
foreign/python/src/client.rs 99.11% <100.00%> (+0.11%) ⬆️
foreign/python/src/lib.rs 100.00% <100.00%> (ø)
foreign/python/src/user.rs 93.02% <93.02%> (ø)

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

@ethanlin01x

Copy link
Copy Markdown
Author

The 3 lines Codecov flags as uncovered in foreign/python/src/user.rs are the #[pyclass(...)] and #[pymethods] attribute lines. They are not missing tests. All hand-written logic in the file is fully exercised by tests/test_user.py.

Should these attribute lines be brought to 100% as well, or is it fine to leave them as is?

@slbotbm

slbotbm commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

The 3 lines Codecov flags as uncovered in foreign/python/src/user.rs are the #[pyclass(...)] and #[pymethods] attribute lines. They are not missing tests. All hand-written logic in the file is fully exercised by tests/test_user.py.
Should these attribute lines be brought to 100% as well, or is it fine to leave them as is?

Fine as it is

Comment thread foreign/python/tests/test_user.py Outdated
)
assert user_by_name is None

user_by_id = await iggy_client.get_user(999999)

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 is not stable. There could be a user that has user id 999999.

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.

In a continuously used server, the ID could eventually come into existence.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. The test now creates a user, deletes it, and asserts the vacated id returns None, so it no longer depends on any hardcoded id.
d41fb76

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.

Here, you do not test username and password length boundaries. Add tests for the following for update_user and create_user:

  • 2-byte and 51-byte usernames failing.
  • 2-byte and 101-byte passwords failing.
  • Non-ascii characters like Japanese, Korean, Thai, emojis. The choice of languages to test is up to you.

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 add the error-before-login pattern to the rest of the tests that do not have this.

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.

For create_user: add the following tests:

  • An inactive user cannot log in even with the correct password
  • A deleted user cannot start a fresh authenticated session.

For get_user:

  • Empty string identifiers failing locally.
  • Negative and greater-than-u32 integer identifiers failing.

For get_users:

  • It returns the users in a strict ascending order, so please assert that as well if not yet asserted.
  • Do not assert absolute counts since the server may contain other users as well.

For update_user:

  • Calling with both optional fields as None and verifying the user remains unchanged, because Rust permits that no-op request.
  • Rejected updates preserving both the original username and status. (could be folded into existing tests)

For delete_user:

  • Root deletion failing by username and numeric ID 0.
  • Deleting the same user twice failing on the second call.
  • Deleting an inactive user. (could be folded into existing happy path test)
  • A live session owned by the deleted user failing its next operation.
  • The deleted username being reusable without old password state.
  • The target disappearing from both get_user() and get_users().

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 remove the match=... blocks in error catching blocks since those messages could change in the future.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the detailed review!

  1. Added the boundary tests in a671663: 2/51-byte usernames and 2/101-byte passwords failing, plus Japanese, Korean, Thai and emoji credentials within the byte limits succeeding and over-limit ones failing.

  2. error-before-login: the parametrized test_user_management_requires_connection_and_auth already covers all five methods. Let me know if you meant additional scenarios beyond that.

  3. Added the per-method tests in da1d23a (inactive/deleted login, local identifier validation, ascending get_users order, update noop, and the delete_user cases). One behavioral note: deleting a user does not invalidate its live session outright, and a regular user has no permissions to lose, so the test asserts get_user on itself flipping to None plus privileged calls staying rejected.

  4. Removed the match= blocks in 3d899e7; only the exception type is asserted now.

Comment thread foreign/python/tests/test_user.py Outdated

from .utils import get_server_config, wait_for_ping, wait_for_server

# Server-side limits: usernames are 3-50 characters, passwords 3-100.

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.

It is not 3-50 characters, but 3-50 bytes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed the comment and added MIN/MAX constants for both limits in bytes. The byte semantics are now covered by tests as well.
a671663

Comment thread foreign/python/tests/test_user.py Outdated
assert isinstance(created.id, int)
assert created.username == username
assert created.status == UserStatus.Active
assert created.created_at > 0

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.

Do not assert created_at

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed
3d899e7

Comment thread foreign/python/tests/test_user.py Outdated
assert first_username in usernames
assert second_username in usernames
assert all(isinstance(user.id, int) for user in users)
assert all(user.created_at > 0 for user in users)

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.

Do not assert creation date

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed
3d899e7

Comment thread foreign/python/tests/test_user.py Outdated
first = await iggy_client.get_users()
second = await iggy_client.get_users()

assert [user.id for user in first] == [user.id for user in second]

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.

For a persistent/shared server, this will not hold.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the test. Its remaining value (a stable view of a single user) is already covered by the repeated get_user test, and the ordering guarantee is now asserted directly via the strictly ascending id check.

@slbotbm

slbotbm commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Also, please do commit the new changes as new commits. This will help me in understanding what changed after my review.

@slbotbm

slbotbm commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

/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 19, 2026
Server error messages are not a stable contract and created_at is
server generated, so assert neither.
Replace the hardcoded nonexistent id with a freshly vacated one, drop
the repeated get_users snapshot comparison that other clients can
invalidate, and assert the strictly ascending id order instead.
The server validates username and password length in bytes (3-50 and
3-100 respectively), not characters. Exercise both bounds for
create_user and update_user, including multibyte credentials that fit
the byte limits and ones that exceed them while staying within the
character count.
Inactive and deleted users cannot log in, invalid identifiers fail
client-side without a server round trip, an update with no fields is
accepted as a noop, and deletion covers root protection, double
deletion, removal from listings, live session behavior, and username
reuse with fresh credentials.
@ethanlin01x

Copy link
Copy Markdown
Author

/ready

@ethanlin01x
ethanlin01x requested a review from slbotbm July 19, 2026 10:53
@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 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review PR is waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[python sdk] Add functions related to user management

2 participants