Skip to content

Conversation

@abdelrahman-zaki
Copy link
Contributor

Explain your changes

This PR updates the ManagementClient to align the users get, update, and delete endpoints with the Management API spec:

  • Uses /api/v1/user?id={id} instead of /api/v1/users/{user_id}
  • Prevents 404 errors when retrieving, updating, or deleting a user
  • Keeps list and sub-resource endpoints unchanged

Fixes #89

Checklist

🛟 If you need help, consider asking for advice over in the Kinde community.

The Management API defines GET/PATCH/DELETE on `/api/v1/user` with `id`
as a query parameter (not `/api/v1/users/{user_id}`). This updates the
users endpoints to match the spec and prevents 404s.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

Walkthrough

Updated ManagementClient endpoint definitions and method generation: users.get, users.update, and users.delete now use /api/v1/user with id passed as a query parameter; dynamic method generation and request construction were adjusted to support optional per-endpoint query_keys.

Changes

Cohort / File(s) Summary
Users endpoints & API endpoint shape
kinde_sdk/management/management_client.py
API_ENDPOINTS['users'] modified: get, update, delete now use path /api/v1/user (caller supplies id as query param); update declared with query_keys ['id']. create remains /api/v1/user; list remains /api/v1/users.
Dynamic method generation & request handling
kinde_sdk/management/management_client.py
_generate_methods now unpacks 2- or 3-tuple endpoints and forwards query_keys; _create_api_method signature adds query_keys; request construction separates query params vs JSON body per HTTP method; headers/auth and serialization adjustments applied.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant ManagementClient
  participant KindeAPI

  Note over App,ManagementClient: Get user (updated)
  App->>ManagementClient: get_user(id)
  ManagementClient->>KindeAPI: GET /api/v1/user?id={id}
  KindeAPI-->>ManagementClient: 200 JSON
  ManagementClient-->>App: user object

  rect rgba(200,230,255,0.25)
    Note over App,ManagementClient: Update user (updated)
    App->>ManagementClient: update_user(id, payload)
    ManagementClient->>KindeAPI: PUT /api/v1/user?id={id} (application/json)
    KindeAPI-->>ManagementClient: 200 JSON
    ManagementClient-->>App: updated user
  end

  rect rgba(255,230,230,0.25)
    Note over App,ManagementClient: Delete user (updated)
    App->>ManagementClient: delete_user(id)
    ManagementClient->>KindeAPI: DELETE /api/v1/user?id={id}
    alt success
      KindeAPI-->>ManagementClient: 204 No Content
      ManagementClient-->>App: success
    else error
      KindeAPI-->>ManagementClient: 4xx/5xx
      ManagementClient-->>App: error
    end
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Align Management Client users.get endpoint with Kinde API (#89)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Updated users.update to use query param path and added query_keys (kinde_sdk/management/management_client.py) Issue #89 requested fixing the get endpoint only; changing update is not specified.
Updated users.delete to use query param path (kinde_sdk/management/management_client.py) Issue #89 does not mention delete; this modification goes beyond the reported bug fix.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kinde_sdk/management/management_client.py (1)

441-469: Always pass query params (incl. PATCH) and alias user_id→id; remove unused final_path.

  • Ensures id is encoded via the client serializer.
  • Keeps backward compatibility: positional first arg still works; user_id kwarg is mapped to id.
  • Drops dead code (final_path) that’s never used.

Apply this diff:

@@
-            # Handle query params or body data based on HTTP method
-            query_params = None
-            body = None
-            
-            if http_method in ('GET', 'DELETE'):
-                query_params = {k: v for k, v in kwargs.items() if v is not None}
-            else:
-                body = {k: v for k, v in kwargs.items() if v is not None}
-            
-            # FIXED: Use param_serialize to properly construct the full URL with host
-            # Handle query parameters by appending them to the path
-            final_path = formatted_path
-            if query_params and http_method in ('GET', 'DELETE'):
-                query_string = '&'.join([f"{k}={v}" for k, v in query_params.items() if v is not None])
-                if query_string:
-                    separator = '&' if '?' in final_path else '?'
-                    final_path = f"{final_path}{separator}{query_string}"
+            # Handle query params or body data
+            query_params = None
+            body = None
+
+            if http_method in ('GET', 'DELETE'):
+                query_params = {k: v for k, v in kwargs.items() if v is not None}
+            else:
+                body = {k: v for k, v in kwargs.items() if v is not None}
+
+            # Users get/update/delete require an id in query string; accept id or user_id kwargs
+            # and also the first positional arg for back-compat.
+            if resource == 'users' and action in ('get', 'update', 'delete'):
+                positional_id = args[0] if args else None
+                canonical_id = kwargs.get('id', kwargs.get('user_id', positional_id))
+                if canonical_id is not None:
+                    query_params = (query_params or {})
+                    query_params['id'] = canonical_id
+                    # Ensure id fields don't leak into body or duplicate in query
+                    if body:
+                        body.pop('id', None)
+                        body.pop('user_id', None)
+                    query_params.pop('user_id', None)
@@
-            method, url, header_params, body, post_params = self.api_client.param_serialize(
+            _serialized_method, url, header_params, body, post_params = self.api_client.param_serialize(
                 method=http_method,
                 resource_path=resource_path_for_serialize,  # Use path without /api/v1 prefix
-                query_params=query_params if http_method in ('GET', 'DELETE') else None,
+                query_params=query_params or None,
                 header_params={},
                 body=body if http_method not in ('GET', 'DELETE') else None
             )

I can add focused unit tests for get_user/update_user/delete_user to assert the final serialized URL carries ?id=... and that kwargs don’t duplicate/bleed into the body. Want me to push those?

🧹 Nitpick comments (1)
kinde_sdk/management/management_client.py (1)

509-518: Docstring param name clarity.

For users endpoints, consider documenting that id (alias: user_id) can be passed positionally or via kwargs to avoid confusion after the switch to query param.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 721663f and 258f19f.

📒 Files selected for processing (1)
  • kinde_sdk/management/management_client.py (1 hunks)

@codecov
Copy link

codecov bot commented Sep 1, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ect request serialization

- Add optional `query_keys` to API_ENDPOINTS signatures to declare which kwargs
  must be lifted into the query string (e.g. `('PATCH', '/api/v1/user', ['id'])`).
- Update `_generate_methods` and `_create_api_method` to handle the 2- vs 3-tuple
  endpoint forms and to route declared `query_keys` to `query_params`.
- Ensure `get_user` / `update_user` work with `/api/v1/user` by passing `id` as a
  query parameter (no path templating required).
- Set default headers for write methods (`Content-Type` / `Accept` = application/json).
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kinde_sdk/management/management_client.py (1)

436-457: Breaking change: positional args to get/update/delete user are now ignored. Map args → query_keys for back-compat and validate required keys.

Previously, get_user("abc") filled {user_id}. With /api/v1/user (no path placeholder), the current logic drops positional args and sends no id. Map positional args to declared query_keys (e.g., ['id']) and fail fast if required keys are missing. Also allow callers to pass _request_timeout without leaking it into query/body.

         def api_method(*args, **kwargs) -> Dict[str, Any]:
             # Format path with any path parameters from args
             formatted_path = path
             if '{' in path and args:
                 param_values = list(args)
                 while '{' in formatted_path and param_values:
                     start_idx = formatted_path.find('{')
                     end_idx = formatted_path.find('}')
                     if start_idx >= 0 and end_idx >= 0:
                         formatted_path = formatted_path[:start_idx] + str(param_values.pop(0)) + formatted_path[end_idx + 1:]
 
+            # Allow method-level timeout without polluting query/body
+            _request_timeout = kwargs.pop('_request_timeout', None)
+
+            # Back-compat: if no placeholders but we have declared query_keys, map positional args -> query keys
+            if '{' not in path and args and query_keys:
+                for i, key in enumerate(query_keys):
+                    if i < len(args) and key not in kwargs:
+                        kwargs[key] = args[i]
+
+            # Validate required query keys when declared
+            if query_keys:
+                missing = [k for k in query_keys if kwargs.get(k) is None]
+                if missing:
+                    raise ValueError(f"Missing required query parameter(s): {', '.join(missing)}")
             # Handle query/body split
             if http_method in ('GET', 'DELETE'):
                 query_params = {k: v for k, v in kwargs.items() if v is not None}
                 payload = None
             else:
                 # Lift ONLY declared query_keys into the query string
                 qset = set(query_keys or ())
                 query_params = {k: kwargs.pop(k) for k in list(kwargs) if k in qset and kwargs[k] is not None}
                 # Remaining kwargs go to JSON body
                 payload = {k: v for k, v in kwargs.items() if v is not None}

And pass the timeout to the request (see comment below).

🧹 Nitpick comments (3)
kinde_sdk/management/management_client.py (3)

458-466: Remove dead manual query-string assembly; param_serialize already handles it.

This block builds final_path but it’s never used. Keeping it risks later double-encoding/duplication.

-            # Handle query parameters by appending them to the path
-            final_path = formatted_path
-            if query_params and http_method in ('GET', 'DELETE'):
-                query_string = '&'.join([f"{k}={v}" for k, v in query_params.items() if v is not None])
-                if query_string:
-                    separator = '&' if '?' in final_path else '?'
-                    final_path = f"{final_path}{separator}{query_string}"

489-496: Honor caller-specified timeout.

Wire the _request_timeout extracted in the method to avoid infinite hangs.

-                _request_timeout=None
+                _request_timeout=_request_timeout

522-522: Docstrings: prefer declared query_keys for parameter name (use id).

Without placeholders, the docs currently say user_id. Prefer id when query_keys exist to match the API.

-            param_name = path.split('{')[-1].split('}')[0] if '{' in path else f"{resource_singular}_id"
+            param_name = path.split('{')[-1].split('}')[0] if '{' in path else (query_keys[0] if query_keys else f"{resource_singular}_id")

Apply the same change to the update and delete branches.

Also applies to: 544-544, 556-556

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f77e6ae and 4f5def6.

📒 Files selected for processing (1)
  • kinde_sdk/management/management_client.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
kinde_sdk/management/management_client.py (2)
kinde_sdk/management/api_client.py (1)
  • param_serialize (142-248)
kinde_sdk/management/management_token_manager.py (1)
  • get_access_token (223-231)
🔇 Additional comments (2)
kinde_sdk/management/management_client.py (2)

396-402: 3-tuple endpoint support is clean and readable.

Good extensibility; preserves 2-tuple handling and gracefully unpacks query_keys when present.


471-477: Good use of param_serialize with host and query_params.

The /api/v1 stripping is correct given Configuration(host=self.base_url) includes /api/v1.

@brettchaldecott
Copy link
Contributor

Looks good, I am happy and accepting it

@brettchaldecott brettchaldecott merged commit 6ab3739 into kinde-oss:main Sep 3, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Management Client's Get User endpoint does not match Kinde's actual API

2 participants