Skip to content

fix: update Python docs to match SDK - #763

Open
Koosha-Owji wants to merge 1 commit into
mainfrom
fix/python-sdk-management-docs
Open

fix: update Python docs to match SDK#763
Koosha-Owji wants to merge 1 commit into
mainfrom
fix/python-sdk-management-docs

Conversation

@Koosha-Owji

@Koosha-Owji Koosha-Owji commented Jul 11, 2026

Copy link
Copy Markdown
Member

Description (required)

Fixes the Python SDK docs to match the SDK.

  • oauth.get_management() → the real, synchronous ManagementClient
  • Async await management.get_users() → actual sync calls (management.users_api.get_users()), with correct method names and params
  • Added an organization invites example
  • Error-handling section now uses real exceptions (kinde_sdk.core.exceptions, kinde_sdk.management.exceptions) instead of the nonexistent kinde_sdk.exceptions

All code samples checked against the current SDK.

Related issues & labels (optional)

  • Closes #
  • Suggested label:

Summary by CodeRabbit

  • Documentation
    • Updated Python SDK guidance for the v2+ architecture.
    • Added instructions for creating and using the synchronous Management Client with M2M credentials.
    • Revised Management API examples, resource access patterns, and error-handling guidance.
    • Updated authentication, authorization, permission-checking, and FastAPI exception-handling examples.
    • Clarified exception types and how to inspect API error details.

@Koosha-Owji
Koosha-Owji requested a review from a team as a code owner July 11, 2026 20:06
@github-actions github-actions Bot added the sdk label Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The Python SDK documentation now reflects v2+ architecture, including new exception modules, synchronous ManagementClient construction, revised Management API examples, updated permission checks, and FastAPI handling for Management API exceptions.

Changes

Python SDK v2 guidance

Layer / File(s) Summary
Management API client guidance
src/content/docs/developer-tools/sdks/backend/python-sdk.mdx
Migration instructions and Management API examples now use the new exception modules and a synchronous ManagementClient configured with M2M credentials.
Exception and permission patterns
src/content/docs/developer-tools/sdks/backend/python-sdk.mdx
Error-handling guidance separates core authentication exceptions from Management API ApiException subclasses and updates authentication and permission-checking examples.
FastAPI error-handling setup
src/content/docs/developer-tools/sdks/backend/python-sdk.mdx
The complete setup example adds an ApiException handler and updates protected-route authentication and permission logic.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

A bunny reads the SDK anew,
With clients built in sync-time too.
Exceptions hop in tidy rows,
Permissions bloom where logic grows.
FastAPI guards the door—
Fresh v2 docs to explore!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: updating Python documentation to align with the current SDK.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/python-sdk-management-docs

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.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying kinde-docs-preview with  Cloudflare Pages  Cloudflare Pages

Latest commit: d49c202
Status: ✅  Deploy successful!
Preview URL: https://4dad3a4a.kinde-docs-preview.pages.dev
Branch Preview URL: https://fix-python-sdk-management-do.kinde-docs-preview.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/content/docs/developer-tools/sdks/backend/python-sdk.mdx`:
- Around line 1505-1520: Update both FastAPI route examples, including
list_organizations and the corresponding route around the second
ManagementClient example, so synchronous management API calls do not run
directly inside async handlers. Prefer changing each route declaration from
async def to def, preserving the existing synchronous management calls and
response behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 072a4a3c-ae2c-4718-a893-c800c25b4507

📥 Commits

Reviewing files that changed from the base of the PR and between 886d14b and d49c202.

📒 Files selected for processing (1)
  • src/content/docs/developer-tools/sdks/backend/python-sdk.mdx

Comment on lines 1505 to 1520
```python
from kinde_sdk.auth.async_oauth import AsyncOAuth
from fastapi import FastAPI
from kinde_sdk.management import ManagementClient

oauth = AsyncOAuth()
app = FastAPI()
management = ManagementClient(
domain="your-domain.kinde.com",
client_id="your-m2m-client-id",
client_secret="your-m2m-client-secret",
)

async def manage_organizations():
# Get management client
management = await oauth.get_management()

# List organizations
orgs = await management.get_organizations()

# Get a specific organization
org = await management.get_organization(org_id="org_123")

# Create a new organization
new_org = await management.create_organization(
name="My Organization"
)

# Update an organization
updated_org = await management.update_organization(
org_id="org_123",
name="Updated Name"
)

# Delete an organization
await management.delete_organization(org_id="org_123")

return orgs
@app.get("/organizations")
async def list_organizations():
# The management call itself is synchronous
return management.organizations_api.get_organizations()
```

@coderabbitai coderabbitai Bot Jul 11, 2026

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Synchronous ManagementClient calls in async FastAPI routes block the event loop.

Both examples define async def routes but call synchronous management.* methods directly. This blocks the event loop for the duration of each HTTP call to Kinde's API, starving concurrent requests. The comment on line 1518 acknowledges the call is synchronous but doesn't address the blocking consequence.

Either define the route as a regular def (FastAPI automatically runs sync routes in a threadpool) or offload the call with asyncio.to_thread():

⚡ Proposed fix — use `def` instead of `async def` (simplest)
 `@app.get`("/organizations")
-async def list_organizations():
+def list_organizations():
     # The management call itself is synchronous
     return management.organizations_api.get_organizations()
 `@app.get`("/users/{user_id}")
-async def get_user(user_id: str):
+def get_user(user_id: str):
     try:
         return management.users_api.get_user_data(id=user_id)
     except NotFoundException:
         raise HTTPException(status_code=404, detail="User not found")
     except ApiException as e:
         raise HTTPException(status_code=e.status, detail=e.reason)

Alternatively, if the route must stay async:

⚡ Alternative fix — offload with `asyncio.to_thread`
 `@app.get`("/organizations")
 async def list_organizations():
-    # The management call itself is synchronous
-    return management.organizations_api.get_organizations()
+    return await asyncio.to_thread(management.organizations_api.get_organizations)

Also applies to: 1538-1546

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/content/docs/developer-tools/sdks/backend/python-sdk.mdx` around lines
1505 - 1520, Update both FastAPI route examples, including list_organizations
and the corresponding route around the second ManagementClient example, so
synchronous management API calls do not run directly inside async handlers.
Prefer changing each route declaration from async def to def, preserving the
existing synchronous management calls and response behavior.

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.

Valid, needs a fix.

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.

@dtoxvanilla1991, acknowledged. The examples need a fix.

  • Change the affected FastAPI Management API route handlers from async def to def.
  • Keep the existing synchronous ManagementClient calls unchanged.
  • Validate both Management API code examples after the edit.

🐇 ✅


  • 📌 Commit the changes to this branch

You are interacting with an AI system.

@dtoxvanilla1991 dtoxvanilla1991 left a comment

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.

Good foundation. 4 comments to review. 📔

Comment on lines 1654 to +1664
from fastapi import HTTPException
from kinde_sdk.auth.oauth import OAuth
from kinde_sdk.exceptions import KindeAPIException

oauth = OAuth(framework="fastapi", app=app)
from kinde_sdk.auth import permissions

async def check_permission_with_error_handling(request: Request, permission: str):
try:
perm_result = await oauth.get_permission(permission, request)
if not perm_result.get("isGranted"):
raise HTTPException(
status_code=403,
detail=f"Permission '{permission}' not granted"
)
return perm_result
except KindeAPIException as e:
async def require_permission(permission_key: str):
result = await permissions.get_permission(permission_key)
if not result.get("isGranted"):
raise HTTPException(
status_code=e.status_code,
detail=f"Error checking permission: {str(e)}"
status_code=403,
detail=f"Permission '{permission_key}' not granted",
)
return result

@dtoxvanilla1991 dtoxvanilla1991 Aug 10, 2026

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 introduces a brand new API shape for permission checks - from kinde_sdk.auth import permissions then await permissions.get_permission(permission_key), no oauth instance, no request. Every other permission check in this file (the Authorization section, plus the untouched examples further down) uses oauth.get_permission(permission, request). Two contradicting ways to do the same thing on the same page is going to confuse whoever reads this next.

Since this PR's whole premise is "match the docs to the real SDK," worth double-checking whether kinde_sdk.auth.permissions is actually the current interface or a mix-up with the oauth.get_permission() pattern used everywhere else on this page. Pick one and make the rest of the file consistent with it.

Comment on lines 1614 to +1618
@app.get("/protected")
async def protected_route(request: Request):
try:
# Check authentication
if not await oauth.is_authenticated(request):
raise HTTPException(status_code=401, detail="Not authenticated")

# Get user info
user_info = await oauth.get_user_info(request)
return user_info

except KindeAuthenticationException as e:
# Handle authentication errors
raise HTTPException(status_code=401, detail=f"Authentication failed: {str(e)}")

except KindeAuthorizationException as e:
# Handle authorization errors
raise HTTPException(status_code=403, detail=f"Authorization failed: {str(e)}")

except KindeAPIException as e:
# Handle API errors
raise HTTPException(status_code=e.status_code, detail=f"API error: {str(e)}")

except Exception as e:
# Handle unexpected errors
raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}")
async def protected_route():
if not oauth.is_authenticated():
raise HTTPException(status_code=401, detail="Not authenticated")
return oauth.get_user_info()

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 one's calling oauth.is_authenticated() and oauth.get_user_info() with no await and no request arg. Every other FastAPI + OAuth example still left in this same doc (lines ~510, 571, 669, 714, 1663, 1826 on main) awaits these calls and passes request. That's not a small stylistic gap - it's two different call signatures for the same method on the same client in the same file.

If the sync/no-request form is actually correct for the current SDK, the older examples elsewhere in this file need updating too, otherwise readers won't know which one to trust.

Comment on lines 1538 to 1546
@app.get("/users/{user_id}")
async def get_user(user_id: str):
management = oauth.get_management()
try:
user = await management.get_user(user_id=user_id)
return user
except KindeAPIException as e:
raise HTTPException(status_code=e.status_code, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}")
```

**Example with AsyncOAuth client:**

```python
from kinde_sdk.auth.async_oauth import AsyncOAuth
from kinde_sdk.exceptions import KindeAPIException

oauth = AsyncOAuth()

async def get_user_safely(user_id: str):
management = await oauth.get_management()
try:
user = await management.get_user(user_id=user_id)
return user
except KindeAPIException as e:
print(f"API Error {e.status_code}: {e.message}")
return None
except Exception as e:
print(f"Unexpected error: {str(e)}")
return None
return management.users_api.get_user_data(id=user_id)
except NotFoundException:
raise HTTPException(status_code=404, detail="User not found")
except ApiException as e:
raise HTTPException(status_code=e.status, detail=e.reason)
```

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.

Same blocking issue as the list_organizations example above - async def get_user calling management.users_api.get_user_data() directly. Two occurrences of the same anti-pattern in one section means a reader who fixes one and misses the other still ships a half-broken example. Worth fixing both in the same commit.

Comment on lines 1505 to 1520
```python
from kinde_sdk.auth.async_oauth import AsyncOAuth
from fastapi import FastAPI
from kinde_sdk.management import ManagementClient

oauth = AsyncOAuth()
app = FastAPI()
management = ManagementClient(
domain="your-domain.kinde.com",
client_id="your-m2m-client-id",
client_secret="your-m2m-client-secret",
)

async def manage_organizations():
# Get management client
management = await oauth.get_management()

# List organizations
orgs = await management.get_organizations()

# Get a specific organization
org = await management.get_organization(org_id="org_123")

# Create a new organization
new_org = await management.create_organization(
name="My Organization"
)

# Update an organization
updated_org = await management.update_organization(
org_id="org_123",
name="Updated Name"
)

# Delete an organization
await management.delete_organization(org_id="org_123")

return orgs
@app.get("/organizations")
async def list_organizations():
# The management call itself is synchronous
return management.organizations_api.get_organizations()
```

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.

Valid, needs a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants