Skip to content

Conversation

@DanielRivers
Copy link
Member

Explain your changes

This updates how the management client is initialised.

Instead of defining each route, it handles directly from the generated code.

Checklist

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

@DanielRivers DanielRivers requested a review from a team as a code owner January 8, 2026 13:07
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Walkthrough

The ManagementClient is refactored to dynamically discover and instantiate API classes from a dedicated module, replacing hard-coded endpoint mappings. A token injection wrapper is added to automatically insert Bearer tokens into API calls, and backward-compatibility methods are provided as deprecated proxies to the new API namespaces.

Changes

Cohort / File(s) Change Summary
ManagementClient refactoring
kinde_sdk/management/management_client.py
Removes static API_ENDPOINTS and RESPONSE_TYPES declarations; replaces with dynamic API class discovery via reflection. Adds _initialize_api_classes() to instantiate all *Api classes and attach them to the client using snake_case naming (e.g., UsersApiusers). Implements automatic token injection by wrapping ApiClient.call_api() to extract and inject Bearer tokens from ManagementTokenManager. Introduces _class_name_to_snake_case() helper for attribute naming. Replaces static base URL composition with host-based configuration. Adds 22 deprecated wrapper methods (get_users, create_user, get_organizations, create_role, etc.) that delegate to dynamically loaded API namespaces and emit DeprecationWarning.

Sequence Diagram

sequenceDiagram
    participant Client as Application Client
    participant MC as ManagementClient
    participant Wrapper as Token Injection Wrapper
    participant API as Dynamic API Instance<br/>(UsersApi, etc.)
    participant Token as ManagementTokenManager
    participant AC as ApiClient

    Client->>MC: Call api method<br/>(e.g., users.get_users())
    MC->>API: Route to dynamic API instance
    API->>Wrapper: make_request()
    Wrapper->>Token: get_token()
    Token-->>Wrapper: Bearer token
    Wrapper->>AC: call_api() with<br/>Authorization header
    AC->>AC: Execute API call
    AC-->>Wrapper: Response
    Wrapper-->>API: Return response
    API-->>MC: Return result
    MC-->>Client: Return result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: More dry approach to API handling' accurately captures the main change: refactoring the management client to use dynamic API class loading instead of hard-coded route definitions.
Description check ✅ Passed The description explains that the management client initialization now handles routes from generated code instead of defining each route manually, which aligns with the changeset's refactoring of API handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 4

🤖 Fix all issues with AI agents
In @kinde_sdk/management/management_client.py:
- Around line 100-123: The token-injection wrapper in _setup_token_handling must
handle failures from token_manager.get_access_token() and avoid silently
overwriting existing Authorization headers: update call_api_with_token to
try/except around self.token_manager.get_access_token(), raise or return a clear
AuthenticationError (or raise ValueError) if the call raises or returns a falsy
token instead of injecting "Bearer None", and before setting
kwargs['header_params']['Authorization'] check for an existing Authorization
value and either preserve it or log/raise a warning so you don't overwrite it
silently; ensure the error you raise is descriptive (e.g., "Failed to acquire
access token") and replace api_client.call_api only after this robust wrapper is
implemented.
- Line 281: The parameter type for get_organization should explicitly allow
None; update the signature of get_organization to use an explicit Optional type
(e.g., Optional[str] or Python 3.10+ union syntax str | None) instead of relying
on a default None, and add the corresponding import (from typing import
Optional) if using Optional to satisfy PEP 484.
- Around line 185-495: Add explicit Optional[type] annotations for parameters
that default to None: import typing.Optional and the corresponding request
models (e.g., CreateUserRequest, CreateOrganizationRequest, CreateRoleRequest,
CreateFeatureFlagRequest, UpdateUserRequest, UpdateRoleRequest,
UpdateFeatureFlagRequest) and update signatures such as
create_user(create_user_request: Optional[CreateUserRequest] = None, **kwargs),
create_organization(create_organization_request:
Optional[CreateOrganizationRequest] = None, **kwargs),
create_role(create_role_request: Optional[CreateRoleRequest] = None, **kwargs),
create_feature_flag(create_feature_flag_request:
Optional[CreateFeatureFlagRequest] = None, **kwargs), update_user(id: str,
update_user_request: Optional[UpdateUserRequest] = None, **kwargs),
update_role(role_id: str, update_role_request: Optional[UpdateRoleRequest] =
None, **kwargs), and update_feature_flag(feature_flag_key: str,
update_feature_flag_request: Optional[UpdateFeatureFlagRequest] = None,
**kwargs); keep the existing **kwargs forwarding behavior unchanged.
🧹 Nitpick comments (1)
kinde_sdk/management/management_client.py (1)

154-178: Consider handling consecutive uppercase letters in snake_case conversion.

The current regex r'(?<!^)(?=[A-Z])' will convert consecutive uppercase letters individually, which may create non-intuitive attribute names for acronym-heavy classes:

  • APIsApia_p_i_s (might prefer apis)
  • MFAApim_f_a (might prefer mfa)

This works correctly but could be refined if more natural names are preferred.

♻️ Optional enhancement for acronym handling
 @staticmethod
 def _class_name_to_snake_case(class_name: str) -> str:
     """
     Convert a class name to snake_case attribute name.
     
     Examples:
     - UsersApi -> users
     - FeatureFlagsApi -> feature_flags
     - APIsApi -> apis
     - MFAApi -> mfa
     
     Args:
         class_name: The class name (e.g., 'UsersApi')
         
     Returns:
         Snake case attribute name (e.g., 'users')
     """
     # Remove the 'Api' suffix
     name = class_name[:-3] if class_name.endswith('Api') else class_name
     
-    # Insert underscores before uppercase letters (except at the start)
-    # and convert to lowercase
-    snake_case = re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()
+    # Handle sequences of uppercase letters followed by lowercase
+    # (e.g., "HTTPSConnection" -> "HTTPS_Connection")
+    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+    # Handle transitions from lowercase/digit to uppercase
+    # (e.g., "getHTTPResponseCode" -> "get_HTTPResponseCode")
+    snake_case = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
     
     return snake_case

This would produce:

  • APIsApiapis
  • MFAApimfa
  • HTTPSConnectionApihttps_connection
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7deb113 and bb861f2.

📒 Files selected for processing (1)
  • kinde_sdk/management/management_client.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-21T04:46:50.368Z
Learnt from: KomanRudden
Repo: kinde-oss/kinde-python-sdk PR: 101
File: kinde_sdk/auth/permissions.py:6-7
Timestamp: 2025-08-21T04:46:50.368Z
Learning: The kinde_sdk has both frontend and management API modules. The frontend permissions_api.py exists at kinde_sdk/frontend/api/permissions_api.py and contains PermissionsApi class. Using the management API instead of the frontend API for user permissions would be incorrect.

Applied to files:

  • kinde_sdk/management/management_client.py
🪛 GitHub Actions: CI
kinde_sdk/management/management_client.py

[error] 231-231: pydantic.ValidationError: create_user received unexpected keyword arguments (email, given_name, family_name) when calling UsersApi.create_user.

🪛 Ruff (0.14.10)
kinde_sdk/management/management_client.py

281-281: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

🔇 Additional comments (1)
kinde_sdk/management/management_client.py (1)

19-20: The api module at kinde_sdk/management/api does not exist, causing a ModuleNotFoundError when management_client.py is imported.

Lines 19-20 attempt to import from kinde_sdk.management import api, but neither the api directory nor an api.py file exists in the repository. Additionally, the __init__.py file imports classes from this non-existent module (e.g., from kinde_sdk.management.api.users_api import UsersApi), so the management module cannot be imported at all. Either the api module needs to be created with the expected structure, or the dynamic loading approach needs to be revised to work with the actual module layout.

⛔ Skipped due to learnings
Learnt from: KomanRudden
Repo: kinde-oss/kinde-python-sdk PR: 101
File: kinde_sdk/auth/permissions.py:6-7
Timestamp: 2025-08-21T04:46:50.368Z
Learning: The kinde_sdk has both frontend and management API modules. The frontend permissions_api.py exists at kinde_sdk/frontend/api/permissions_api.py and contains PermissionsApi class. Using the management API instead of the frontend API for user permissions would be incorrect.

Comment on lines 100 to +123
def _setup_token_handling(self):
"""Set up automatic token refresh for API calls."""
# Token will be added directly in the API method since we're calling REST client directly
pass

def _generate_methods(self):
"""Generate dynamic methods for each API endpoint."""
for resource, endpoints in self.API_ENDPOINTS.items():
# Handle special cases for singularization
if resource == 'business':
resource_singular = 'business' # Don't remove 's' from 'business'
else:
resource_singular = resource[:-1] if resource.endswith('s') else resource

for action, endpoint in endpoints.items():
if len(endpoint) == 3:
method, path, query_keys = endpoint
else:
method, path = endpoint
query_keys = ()

# Create method name based on action and resource
if action == 'list':
method_name = f"get_{resource}"
elif action == 'get':
method_name = f"get_{resource_singular}"
else:
method_name = f"{action}_{resource_singular}"

# Create the method
api_method = self._create_api_method(method, path, resource, action, query_keys)

# Set the method on the class
setattr(self, method_name, api_method)

def _create_api_method(self, http_method: str, path: str, resource: str, action: str, query_keys=()) -> Callable:
"""
Create a dynamic method for an API endpoint.
Set up automatic token injection for all API calls.
Args:
http_method: HTTP method (GET, POST, etc.)
path: API endpoint path
resource: API resource name (users, organizations, etc.)
action: API action (list, get, create, etc.)
Returns:
A callable method that makes the API request
This modifies the API client to automatically add the Bearer token
to all outgoing requests.
"""
# Handle special cases for singularization
if resource == 'business':
resource_singular = 'business' # Don't remove 's' from 'business'
else:
resource_singular = resource[:-1] if resource.endswith('s') else resource

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:]

# 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}

# 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}"

# Use param_serialize to get the proper URL with host
# Remove /api/v1 prefix from resource_path since host already includes it
resource_path_for_serialize = formatted_path.replace('/api/v1', '', 1)

method, url, header_params, serialized_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 or None,
header_params={},
body=payload if http_method not in ('GET', 'DELETE') else None
)

# Ensure required headers for PATCH/POST/PUT
if http_method not in ('GET', 'DELETE'):
header_params.setdefault('Content-Type', 'application/json')
header_params.setdefault('Accept', 'application/json')

# Add the authorization token to headers
original_call_api = self.api_client.call_api

def call_api_with_token(*args, **kwargs):
"""Wrapper that adds authentication token to all API calls."""
# Get the access token
token = self.token_manager.get_access_token()
header_params['Authorization'] = f"Bearer {token}"

# Call the REST client directly with the constructed URL
response = self.api_client.rest_client.request(
method=http_method,
url=url,
headers=header_params,
body=serialized_body if http_method not in ('GET', 'DELETE') else None,
post_params=post_params,
_request_timeout=None
)

# Use the API client's response_deserialize to properly handle the response
# First read the response data
response.read()

# Then deserialize it
api_response = self.api_client.response_deserialize(response, self.RESPONSE_TYPES[resource][action])
# Inject the token into headers
if 'header_params' not in kwargs:
kwargs['header_params'] = {}
kwargs['header_params']['Authorization'] = f"Bearer {token}"

return api_response.data
# Call the original method
return original_call_api(*args, **kwargs)

# Add docstring to the method based on the action and resource
if action == 'list':
docstring = f"""
Get a list of {resource}.
Args:
**kwargs: Optional arguments to pass to the API.
sort (str): Sort {resource} by field. (Optional)
page_size (int): Number of results per page. (Optional)
next_token (str): Token for the next page of results. (Optional)
Returns:
Dict containing {resource} data.
"""
elif action == 'get':
param_name = path.split('{')[-1].split('}')[0] if '{' in path else f"{resource_singular}_id"
docstring = f"""
Get a {resource_singular} by ID.
Args:
{param_name}: The ID of the {resource_singular} to get.
Returns:
Dict containing {resource_singular} data.
"""
elif action == 'create':
docstring = f"""
Create a new {resource_singular}.
Args:
**kwargs: {resource_singular.capitalize()} data to create.
# Replace the call_api method
self.api_client.call_api = call_api_with_token
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for token acquisition and prevent silent header overwrites.

The token injection wrapper has several concerns:

  1. No error handling if get_access_token() fails or returns None - this will result in Authorization: Bearer None being sent.
  2. Silently overwrites any existing Authorization header without checking or warning.
  3. Token fetch failures will propagate as unclear API errors rather than authentication errors.
🛡️ Proposed fix with error handling
 def _setup_token_handling(self):
     """
     Set up automatic token injection for all API calls.
     
     This modifies the API client to automatically add the Bearer token
     to all outgoing requests.
     """
     original_call_api = self.api_client.call_api
     
     def call_api_with_token(*args, **kwargs):
         """Wrapper that adds authentication token to all API calls."""
-        # Get the access token
-        token = self.token_manager.get_access_token()
+        # Get the access token with error handling
+        try:
+            token = self.token_manager.get_access_token()
+            if not token:
+                raise ValueError("Failed to acquire access token: token is empty")
+        except Exception as e:
+            logger.error(f"Token acquisition failed: {e}")
+            raise RuntimeError(f"Failed to authenticate with Kinde API: {e}") from e
         
         # Inject the token into headers
         if 'header_params' not in kwargs:
             kwargs['header_params'] = {}
         kwargs['header_params']['Authorization'] = f"Bearer {token}"
         
         # Call the original method
         return original_call_api(*args, **kwargs)
     
     # Replace the call_api method
     self.api_client.call_api = call_api_with_token

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @kinde_sdk/management/management_client.py around lines 100 - 123, The
token-injection wrapper in _setup_token_handling must handle failures from
token_manager.get_access_token() and avoid silently overwriting existing
Authorization headers: update call_api_with_token to try/except around
self.token_manager.get_access_token(), raise or return a clear
AuthenticationError (or raise ValueError) if the call raises or returns a falsy
token instead of injecting "Bearer None", and before setting
kwargs['header_params']['Authorization'] check for an existing Authorization
value and either preserve it or log/raise a warning so you don't overwrite it
silently; ensure the error you raise is descriptive (e.g., "Failed to acquire
access token") and replace api_client.call_api only after this robust wrapper is
implemented.

Comment on lines +125 to +152
def _initialize_api_classes(self):
"""
Dynamically initialize all API classes from the api module.
This method inspects the api module and creates an instance of each
API class (classes ending with 'Api'), making them available as
snake_case attributes on this client.
For example:
- UsersApi -> self.users
- OrganizationsApi -> self.organizations
- FeatureFlagsApi -> self.feature_flags
"""
# Get all members of the api module
for name, obj in inspect.getmembers(api):
# Check if it's a class and ends with 'Api'
if inspect.isclass(obj) and name.endswith('Api'):
# Convert class name to snake_case attribute name
# e.g., UsersApi -> users, FeatureFlagsApi -> feature_flags
attr_name = self._class_name_to_snake_case(name)

Returns:
Dict containing the created {resource_singular}.
"""
elif action == 'update':
param_name = path.split('{')[-1].split('}')[0] if '{' in path else f"{resource_singular}_id"
docstring = f"""
Update a {resource_singular}.
Args:
{param_name}: The ID of the {resource_singular} to update.
**kwargs: {resource_singular.capitalize()} data to update.
# Initialize the API class with our configured api_client
api_instance = obj(api_client=self.api_client)

Returns:
Dict containing the updated {resource_singular}.
"""
elif action == 'delete':
param_name = path.split('{')[-1].split('}')[0] if '{' in path else f"{resource_singular}_id"
docstring = f"""
Delete a {resource_singular}.
Args:
{param_name}: The ID of the {resource_singular} to delete.
# Set it as an attribute on this client
setattr(self, attr_name, api_instance)

Returns:
Dict containing the result.
"""
else:
docstring = f"""
{action.capitalize()} {resource}.
logger.debug(f"Initialized {name} as client.{attr_name}")
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add error handling and validation for dynamic API class loading.

The dynamic API initialization has several robustness concerns:

  1. No error handling if an API class fails to instantiate - this could happen if a class requires additional constructor parameters beyond api_client.
  2. No validation that attr_name doesn't collide with existing ManagementClient attributes (e.g., domain, api_client, configuration).
  3. Silent success if no API classes are found - the client would appear to work but have no methods.
  4. No verification that discovered classes are actually instantiable or are the expected API classes.
♻️ Proposed fix with validation and error handling
 def _initialize_api_classes(self):
     """
     Dynamically initialize all API classes from the api module.
     
     This method inspects the api module and creates an instance of each
     API class (classes ending with 'Api'), making them available as
     snake_case attributes on this client.
     
     For example:
     - UsersApi -> self.users
     - OrganizationsApi -> self.organizations
     - FeatureFlagsApi -> self.feature_flags
     """
+    # Track loaded APIs for verification
+    loaded_apis = []
+    
     # Get all members of the api module
     for name, obj in inspect.getmembers(api):
         # Check if it's a class and ends with 'Api'
         if inspect.isclass(obj) and name.endswith('Api'):
             # Convert class name to snake_case attribute name
             # e.g., UsersApi -> users, FeatureFlagsApi -> feature_flags
             attr_name = self._class_name_to_snake_case(name)
+            
+            # Check for attribute collisions
+            if hasattr(self, attr_name):
+                logger.warning(
+                    f"Attribute '{attr_name}' already exists on ManagementClient, "
+                    f"skipping {name}"
+                )
+                continue
             
-            # Initialize the API class with our configured api_client
-            api_instance = obj(api_client=self.api_client)
+            # Initialize the API class with error handling
+            try:
+                api_instance = obj(api_client=self.api_client)
+            except Exception as e:
+                logger.error(f"Failed to initialize {name}: {e}")
+                raise RuntimeError(
+                    f"Failed to initialize API class {name}. "
+                    f"This may indicate a compatibility issue with the generated API."
+                ) from e
             
             # Set it as an attribute on this client
             setattr(self, attr_name, api_instance)
+            loaded_apis.append(attr_name)
             
             logger.debug(f"Initialized {name} as client.{attr_name}")
+    
+    # Verify at least some APIs were loaded
+    if not loaded_apis:
+        raise RuntimeError(
+            "No API classes found in kinde_sdk.management.api module. "
+            "This may indicate the SDK was not properly installed or generated."
+        )
+    
+    logger.info(f"Loaded {len(loaded_apis)} API classes: {', '.join(sorted(loaded_apis))}")

Comment on lines +185 to +495
def get_users(self, **kwargs):
"""
Get users.
.. deprecated::
Use :meth:`client.users.get_users()` instead.
For full documentation and parameters, see UsersApi.get_users()
"""
warnings.warn(
"get_users() is deprecated. Use client.users.get_users() instead.",
DeprecationWarning,
stacklevel=2
)
return self.users.get_users(**kwargs)

def get_user_data(self, user_id: str):
"""
Get user data by ID.
.. deprecated::
Use :meth:`client.users.get_user_data()` instead.
For full documentation and parameters, see UsersApi.get_user_data()
"""
warnings.warn(
"get_user_data() is deprecated. Use client.users.get_user_data() instead.",
DeprecationWarning,
stacklevel=2
)
return self.users.get_user_data(user_id=user_id)

def create_user(self, create_user_request=None, **kwargs):
"""
Create a user.
.. deprecated::
Use :meth:`client.users.create_user()` instead.
For full documentation and parameters, see UsersApi.create_user()
"""
warnings.warn(
"create_user() is deprecated. Use client.users.create_user() instead.",
DeprecationWarning,
stacklevel=2
)
return self.users.create_user(create_user_request=create_user_request, **kwargs)

def update_user(self, id: str, update_user_request, **kwargs):
"""
Update a user.
.. deprecated::
Use :meth:`client.users.update_user()` instead.
For full documentation and parameters, see UsersApi.update_user()
"""
warnings.warn(
"update_user() is deprecated. Use client.users.update_user() instead.",
DeprecationWarning,
stacklevel=2
)
return self.users.update_user(id=id, update_user_request=update_user_request, **kwargs)

def delete_user(self, id: str, **kwargs):
"""
Delete a user.
.. deprecated::
Use :meth:`client.users.delete_user()` instead.
For full documentation and parameters, see UsersApi.delete_user()
"""
warnings.warn(
"delete_user() is deprecated. Use client.users.delete_user() instead.",
DeprecationWarning,
stacklevel=2
)
return self.users.delete_user(id=id, **kwargs)

def get_organizations(self, **kwargs):
"""
Get organizations.
.. deprecated::
Use :meth:`client.organizations.get_organizations()` instead.
For full documentation and parameters, see OrganizationsApi.get_organizations()
"""
warnings.warn(
"get_organizations() is deprecated. Use client.organizations.get_organizations() instead.",
DeprecationWarning,
stacklevel=2
)
return self.organizations.get_organizations(**kwargs)

def get_organization(self, code: str = None, **kwargs):
"""
Get an organization.
.. deprecated::
Use :meth:`client.organizations.get_organization()` instead.
For full documentation and parameters, see OrganizationsApi.get_organization()
"""
warnings.warn(
"get_organization() is deprecated. Use client.organizations.get_organization() instead.",
DeprecationWarning,
stacklevel=2
)
return self.organizations.get_organization(code=code, **kwargs)

def create_organization(self, create_organization_request, **kwargs):
"""
Create an organization.
.. deprecated::
Use :meth:`client.organizations.create_organization()` instead.
For full documentation and parameters, see OrganizationsApi.create_organization()
"""
warnings.warn(
"create_organization() is deprecated. Use client.organizations.create_organization() instead.",
DeprecationWarning,
stacklevel=2
)
return self.organizations.create_organization(
create_organization_request=create_organization_request, **kwargs
)

def update_organization(self, org_code: str, **kwargs):
"""
Update an organization.
.. deprecated::
Use :meth:`client.organizations.update_organization()` instead.
For full documentation and parameters, see OrganizationsApi.update_organization()
"""
warnings.warn(
"update_organization() is deprecated. Use client.organizations.update_organization() instead.",
DeprecationWarning,
stacklevel=2
)
return self.organizations.update_organization(org_code=org_code, **kwargs)

def delete_organization(self, org_code: str, **kwargs):
"""
Delete an organization.
.. deprecated::
Use :meth:`client.organizations.delete_organization()` instead.
For full documentation and parameters, see OrganizationsApi.delete_organization()
"""
warnings.warn(
"delete_organization() is deprecated. Use client.organizations.delete_organization() instead.",
DeprecationWarning,
stacklevel=2
)
return self.organizations.delete_organization(org_code=org_code, **kwargs)

def get_roles(self, **kwargs):
"""
Get roles.
.. deprecated::
Use :meth:`client.roles.get_roles()` instead.
For full documentation and parameters, see RolesApi.get_roles()
"""
warnings.warn(
"get_roles() is deprecated. Use client.roles.get_roles() instead.",
DeprecationWarning,
stacklevel=2
)
return self.roles.get_roles(**kwargs)

def get_role(self, role_id: str):
"""
Get a role.
.. deprecated::
Use :meth:`client.roles.get_role()` instead.
For full documentation and parameters, see RolesApi.get_role()
"""
warnings.warn(
"get_role() is deprecated. Use client.roles.get_role() instead.",
DeprecationWarning,
stacklevel=2
)
return self.roles.get_role(role_id=role_id)

def create_role(self, create_role_request=None, **kwargs):
"""
Create a role.
.. deprecated::
Use :meth:`client.roles.create_role()` instead.
For full documentation and parameters, see RolesApi.create_role()
"""
warnings.warn(
"create_role() is deprecated. Use client.roles.create_role() instead.",
DeprecationWarning,
stacklevel=2
)
return self.roles.create_role(create_role_request=create_role_request, **kwargs)

def update_role(self, role_id: str, update_role_request=None, **kwargs):
"""
Update a role.
.. deprecated::
Use :meth:`client.roles.update_role()` instead.
For full documentation and parameters, see RolesApi.update_role()
"""
warnings.warn(
"update_role() is deprecated. Use client.roles.update_role() instead.",
DeprecationWarning,
stacklevel=2
)
return self.roles.update_role(role_id=role_id, update_role_request=update_role_request, **kwargs)

def delete_role(self, role_id: str):
"""
Delete a role.
.. deprecated::
Use :meth:`client.roles.delete_role()` instead.
For full documentation and parameters, see RolesApi.delete_role()
"""
warnings.warn(
"delete_role() is deprecated. Use client.roles.delete_role() instead.",
DeprecationWarning,
stacklevel=2
)
return self.roles.delete_role(role_id=role_id)

def get_feature_flags(self, **kwargs):
"""
Get feature flags.
.. deprecated::
Use :meth:`client.feature_flags.get_feature_flags()` instead.
For full documentation and parameters, see FeatureFlagsApi.get_feature_flags()
"""
warnings.warn(
"get_feature_flags() is deprecated. Use client.feature_flags.get_feature_flags() instead.",
DeprecationWarning,
stacklevel=2
)
return self.feature_flags.get_feature_flags(**kwargs)

def create_feature_flag(self, create_feature_flag_request=None, **kwargs):
"""
Create a feature flag.
.. deprecated::
Use :meth:`client.feature_flags.create_feature_flag()` instead.
For full documentation and parameters, see FeatureFlagsApi.create_feature_flag()
"""
warnings.warn(
"create_feature_flag() is deprecated. Use client.feature_flags.create_feature_flag() instead.",
DeprecationWarning,
stacklevel=2
)
return self.feature_flags.create_feature_flag(
create_feature_flag_request=create_feature_flag_request, **kwargs
)

def update_feature_flag(self, feature_flag_key: str, update_feature_flag_request=None, **kwargs):
"""
Update a feature flag.
.. deprecated::
Use :meth:`client.feature_flags.update_feature_flag()` instead.
For full documentation and parameters, see FeatureFlagsApi.update_feature_flag()
"""
warnings.warn(
"update_feature_flag() is deprecated. Use client.feature_flags.update_feature_flag() instead.",
DeprecationWarning,
stacklevel=2
)
return self.feature_flags.update_feature_flag(
feature_flag_key=feature_flag_key,
update_feature_flag_request=update_feature_flag_request,
**kwargs
)

def delete_feature_flag(self, feature_flag_key: str):
"""
Delete a feature flag.
.. deprecated::
Use :meth:`client.feature_flags.delete_feature_flag()` instead.
For full documentation and parameters, see FeatureFlagsApi.delete_feature_flag()
"""
warnings.warn(
"delete_feature_flag() is deprecated. Use client.feature_flags.delete_feature_flag() instead.",
DeprecationWarning,
stacklevel=2
)
return self.feature_flags.delete_feature_flag(feature_flag_key=feature_flag_key)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find kinde_sdk/management/api -type f -name "*.py" | head -20

Repository: kinde-oss/kinde-python-sdk

Length of output: 129


🏁 Script executed:

ls -la kinde_sdk/management/

Repository: kinde-oss/kinde-python-sdk

Length of output: 965


🏁 Script executed:

rg "class.*Api" kinde_sdk/management/api --type py -B 2 | head -100

Repository: kinde-oss/kinde-python-sdk

Length of output: 134


🏁 Script executed:

rg "class (UsersApi|OrganizationsApi|RolesApi|FeatureFlagsApi)" --type py

Repository: kinde-oss/kinde-python-sdk

Length of output: 52


🏁 Script executed:

head -100 kinde_sdk/management/__init__.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 6907


🏁 Script executed:

rg "UsersApi|OrganizationsApi|RolesApi|FeatureFlagsApi" kinde_sdk/management/management_client.py -B 5 -A 5

Repository: kinde-oss/kinde-python-sdk

Length of output: 9788


🏁 Script executed:

find . -path "./kinde_sdk/management/api/*.py" -type f | head -20

Repository: kinde-oss/kinde-python-sdk

Length of output: 52


🏁 Script executed:

ls -la kinde_sdk/management/api/ 2>/dev/null || echo "Checking alternative locations..."

Repository: kinde-oss/kinde-python-sdk

Length of output: 103


🏁 Script executed:

rg "def create_user|def update_user|def get_users" --type py -A 10 | head -150

Repository: kinde-oss/kinde-python-sdk

Length of output: 3630


🏁 Script executed:

find . -name "*.py" -type f -exec grep -l "class UsersApi" {} \;

Repository: kinde-oss/kinde-python-sdk

Length of output: 52


🏁 Script executed:

git ls-files | grep -i api | head -30

Repository: kinde-oss/kinde-python-sdk

Length of output: 1251


🏁 Script executed:

ls -la kinde_sdk/

Repository: kinde-oss/kinde-python-sdk

Length of output: 582


🏁 Script executed:

cat .openapi-generator/FILES | grep -i "api" | head -40

Repository: kinde-oss/kinde-python-sdk

Length of output: 1636


🏁 Script executed:

ls -la .openapi-generator/

Repository: kinde-oss/kinde-python-sdk

Length of output: 301


🏁 Script executed:

grep -r "class.*Api" kinde_sdk/ --type py | head -20

Repository: kinde-oss/kinde-python-sdk

Length of output: 187


🏁 Script executed:

grep -n "def create_user\|def update_user\|def get_users" kinde_sdk/apis/tags/users_api.py -A 15

Repository: kinde-oss/kinde-python-sdk

Length of output: 135


🏁 Script executed:

grep -n "def create_organization\|def update_organization" kinde_sdk/apis/tags/organizations_api.py -A 15

Repository: kinde-oss/kinde-python-sdk

Length of output: 143


🏁 Script executed:

grep -n "def create_role\|def update_role" kinde_sdk/apis/tags/roles_api.py -A 15

Repository: kinde-oss/kinde-python-sdk

Length of output: 135


🏁 Script executed:

grep -n "def create_feature_flag\|def update_feature_flag" kinde_sdk/apis/tags/feature_flags_api.py -A 15

Repository: kinde-oss/kinde-python-sdk

Length of output: 143


🏁 Script executed:

ls -la kinde_sdk/apis/tags/ 2>/dev/null | head -20

Repository: kinde-oss/kinde-python-sdk

Length of output: 52


🏁 Script executed:

find kinde_sdk -name "users_api.py" -type f

Repository: kinde-oss/kinde-python-sdk

Length of output: 52


🏁 Script executed:

pwd && git status

Repository: kinde-oss/kinde-python-sdk

Length of output: 352


🏁 Script executed:

git sparse-checkout list

Repository: kinde-oss/kinde-python-sdk

Length of output: 1374


🏁 Script executed:

git ls-files | grep "api" | wc -l

Repository: kinde-oss/kinde-python-sdk

Length of output: 73


🏁 Script executed:

cat kinde_sdk/management/__init__.py | head -50

Repository: kinde-oss/kinde-python-sdk

Length of output: 2737


🏁 Script executed:

wc -l kinde_sdk/management/management_client.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 115


🏁 Script executed:

head -100 kinde_sdk/management/management_client.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 3608


🏁 Script executed:

sed -n '1,184p' kinde_sdk/management/management_client.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 6933


🏁 Script executed:

sed -n '184,250p' kinde_sdk/management/management_client.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 2238


🏁 Script executed:

grep -n "def " kinde_sdk/management/management_client.py | tail -30

Repository: kinde-oss/kinde-python-sdk

Length of output: 1509


🏁 Script executed:

cat kinde_sdk/management/management_client.py | sed -n '185,495p' > /tmp/deprecated_methods.txt && wc -l /tmp/deprecated_methods.txt

Repository: kinde-oss/kinde-python-sdk

Length of output: 101


🏁 Script executed:

find . -name "*.py" -type f -path "*/test*" -o -path "*/example*" | head -20

Repository: kinde-oss/kinde-python-sdk

Length of output: 842


🏁 Script executed:

grep -r "create_user\|update_user" --include="*.py" | grep -E "(test|example)" | head -20

Repository: kinde-oss/kinde-python-sdk

Length of output: 2106


🏁 Script executed:

grep -n "pipeline\|failed\|error" kinde_sdk/management/management_client.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 52


🏁 Script executed:

cat testv2/testv2_management/test_management_client.py | head -200

Repository: kinde-oss/kinde-python-sdk

Length of output: 9100


🏁 Script executed:

cat testv2/testv2_management/test_management_client.py | grep -A 30 "test_create_user_with_body_data"

Repository: kinde-oss/kinde-python-sdk

Length of output: 1655


🏁 Script executed:

cat examples/management_client_example.py

Repository: kinde-oss/kinde-python-sdk

Length of output: 11360


🏁 Script executed:

python3 << 'EOF'
import re

# Read the management_client.py file
with open('kinde_sdk/management/management_client.py', 'r') as f:
    content = f.read()

# Extract all deprecated wrapper methods
pattern = r'def (\w+)\(self(?:, ([^)]*))?\):'
matches = re.findall(pattern, content)

# Focus on the deprecated methods (lines 185+)
deprecated_start = content.find('def get_users(self')
deprecated_section = content[deprecated_start:]

# Count methods and analyze kwargs patterns
kwargs_methods = []
for match in re.finditer(r'def (\w+)\(self([^)]*)\):', deprecated_section):
    method_name = match.group(1)
    params = match.group(2)
    if '**kwargs' in params:
        kwargs_methods.append((method_name, params.strip()))

print(f"Total deprecated methods with **kwargs: {len(kwargs_methods)}")
print("\nMethods and their signatures:")
for method, params in kwargs_methods[:10]:
    print(f"  {method}({params})")
EOF

Repository: kinde-oss/kinde-python-sdk

Length of output: 576


🌐 Web query:

Kinde Python SDK OpenAPI generated API kwargs handling

💡 Result:

Short answer: Kinde’s Python SDK uses OpenAPI-generated client methods that accept standard OpenAPI-generator **kwargs (e.g. async_req, _preload_content, _return_http_data_only, _request_timeout, _request_auth). Pass these kwargs to control async behavior, timeouts, response form, and per-request auth; the SDK’s higher-level ManagementClient/clients simply call those generated methods under the hood. [1][2][3]

Common kwargs (supported by OpenAPI‑generated clients)

  • async_req=True — run request in background thread and return a thread/future. [2]
  • _return_http_data_only=True/False — return only parsed data vs (data, status, headers). [2]
  • _preload_content=False — return raw urllib3.HTTPResponse without decoding. [2]
  • _request_timeout=(connect_timeout, read_timeout) or single number. [2]
  • _request_auth=dict — override auth for that call. [2]
  • Any endpoint-specific optional parameters are passed as named args or in **kwargs.

Example (concise)

  • Synchronous, only data:
    client.some_api_call(required1=val, optional2=val, _return_http_data_only=True)
  • Async with timeout:
    thread = client.some_api_call(required1=val, async_req=True, _request_timeout=(3, 10))
    result = thread.get()

Notes

  • The Kinde SDK exposes higher-level async helpers; consult the SDK docs if you prefer their async/await wrappers. [3]
  • If you need custom headers or auth per call, use the generated client kwargs (_request_auth) or set headers on the ApiClient instance. [2]

Sources

  • Kinde Python SDK (GitHub). [1]
  • OpenAPI‑generated Python client method kwargs documentation (example: kubernetes/openapi-generated docs). [2]
  • Kinde Python SDK docs (Python SDK / Management API usage). [3]

Add type hints to request parameter defaults for clarity.

Several request parameters default to None without proper type annotations:

  • create_user(self, create_user_request=None, **kwargs)
  • create_organization(self, create_organization_request, **kwargs)
  • create_role(self, create_role_request=None, **kwargs)
  • create_feature_flag(self, create_feature_flag_request=None, **kwargs)
  • update_user(self, id: str, update_user_request, **kwargs)
  • update_role(self, role_id: str, update_role_request=None, **kwargs)
  • update_feature_flag(self, feature_flag_key: str, update_feature_flag_request=None, **kwargs)

Import the request model types and annotate: create_user_request: Optional[CreateUserRequest] = None.

The **kwargs forwarding is correct—OpenAPI-generated methods are designed to accept standard kwargs (e.g., async_req, _request_timeout, _return_http_data_only).

🧰 Tools
🪛 GitHub Actions: CI

[error] 231-231: pydantic.ValidationError: create_user received unexpected keyword arguments (email, given_name, family_name) when calling UsersApi.create_user.

🪛 Ruff (0.14.10)

281-281: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

🤖 Prompt for AI Agents
In @kinde_sdk/management/management_client.py around lines 185 - 495, Add
explicit Optional[type] annotations for parameters that default to None: import
typing.Optional and the corresponding request models (e.g., CreateUserRequest,
CreateOrganizationRequest, CreateRoleRequest, CreateFeatureFlagRequest,
UpdateUserRequest, UpdateRoleRequest, UpdateFeatureFlagRequest) and update
signatures such as create_user(create_user_request: Optional[CreateUserRequest]
= None, **kwargs), create_organization(create_organization_request:
Optional[CreateOrganizationRequest] = None, **kwargs),
create_role(create_role_request: Optional[CreateRoleRequest] = None, **kwargs),
create_feature_flag(create_feature_flag_request:
Optional[CreateFeatureFlagRequest] = None, **kwargs), update_user(id: str,
update_user_request: Optional[UpdateUserRequest] = None, **kwargs),
update_role(role_id: str, update_role_request: Optional[UpdateRoleRequest] =
None, **kwargs), and update_feature_flag(feature_flag_key: str,
update_feature_flag_request: Optional[UpdateFeatureFlagRequest] = None,
**kwargs); keep the existing **kwargs forwarding behavior unchanged.

)
return self.organizations.get_organizations(**kwargs)

def get_organization(self, code: str = None, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix implicit Optional type hint.

PEP 484 requires explicit Optional or union syntax when a parameter defaults to None.

🔧 Proposed fix
-    def get_organization(self, code: str = None, **kwargs):
+    def get_organization(self, code: Optional[str] = None, **kwargs):

Or using Python 3.10+ union syntax:

-    def get_organization(self, code: str = None, **kwargs):
+    def get_organization(self, code: str | None = None, **kwargs):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_organization(self, code: str = None, **kwargs):
def get_organization(self, code: Optional[str] = None, **kwargs):
Suggested change
def get_organization(self, code: str = None, **kwargs):
def get_organization(self, code: str | None = None, **kwargs):
🧰 Tools
🪛 Ruff (0.14.10)

281-281: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

🤖 Prompt for AI Agents
In @kinde_sdk/management/management_client.py at line 281, The parameter type
for get_organization should explicitly allow None; update the signature of
get_organization to use an explicit Optional type (e.g., Optional[str] or Python
3.10+ union syntax str | None) instead of relying on a default None, and add the
corresponding import (from typing import Optional) if using Optional to satisfy
PEP 484.

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.

3 participants