๐ฅท Using Clientele with Django Ninja
This guide shows you how to scaffold a Python client for a Django Ninja API using Clientele.
๐ก Working Example: See a real Django Ninja application with code examples from this guide in
server_examples/django_ninja/
Prerequisites
- A Django application with Django Ninja installed
- Django Ninja automatically generates OpenAPI schemas (enabled by default)
Step 1: Get Your OpenAPI Schema
Django Ninja automatically generates an OpenAPI schema for your API. By default, it's available at:
http://your-api-domain/api/openapi.json
For local development:
http://localhost:8000/api/openapi.json
The exact path depends on how you've mounted your Django Ninja API in your URLs.
Example Django Ninja Setup
# urls.py
from django.urls import path
from ninja import NinjaAPI
api = NinjaAPI()
# Your endpoints here...
urlpatterns = [
path("api/", api.urls),
]
With this setup, the OpenAPI schema is at: http://localhost:8000/api/openapi.json
Downloading the Schema
You can either:
Option A: Use the URL directly (if the API is accessible):
clientele start-api -u http://localhost:8000/api/openapi.json -o my_client/
Option B: Download the schema file first:
curl http://localhost:8000/api/openapi.json > openapi.json
clientele start-api -f openapi.json -o my_client/
Step 2: Scaffold the Client
clientele start-api -u http://localhost:8000/api/openapi.json -o my_client/
Async Client
Django Ninja supports both sync and async views. If you want an async client:
clientele start-api -u http://localhost:8000/api/openapi.json -o my_client/ --asyncio
Step 3: Use the scaffolded Client
Usage Example
from my_client import client, schemas
# Call a GET endpoint
response = client.list_users_api_users_get()
# Call a POST endpoint with data
user_data = schemas.UserIn(
username="alice",
email="[email protected]"
)
response = client.create_user_api_users_post(data=user_data)
# Handle responses
match response:
case schemas.UserOut():
print(f"User created: {response.username}")
case schemas.Error():
print(f"Error: {response.message}")
Async usage Example
If your Django Ninja views are async:
from my_async_client import client, schemas
async def create_user():
user_data = schemas.UserIn(
username="alice",
email="[email protected]"
)
response = await client.create_user_api_users_post(data=user_data)
return response
Working with operation_id
Django Ninja generates operation_id values for each endpoint, which Clientele uses to create function names.
Default Operation ID
By default, Django Ninja generates operation IDs from the function name and path:
from ninja import NinjaAPI
api = NinjaAPI()
@api.get("/users")
def list_users(request):
return []
This might generate: list_users_api_users_get
Custom operation_id
You can customize the operation ID in Django Ninja to get cleaner function names:
from ninja import NinjaAPI
api = NinjaAPI()
@api.get("/users", operation_id="list_users")
def get_users(request):
return []
@api.post("/users", operation_id="create_user")
def create_user(request, user: UserIn):
return user
This generates:
client.list_users()instead ofclient.list_users_api_users_get()client.create_user()instead ofclient.create_user_api_users_post()
Authentication
See api authentication.
Schemas and Pydantic
Django Ninja uses Pydantic for schemas, which aligns perfectly with Clientele!
Django Ninja Schema
from ninja import Schema
class UserIn(Schema):
username: str
email: str
age: int
class UserOut(Schema):
id: int
username: str
email: str
age: int
created_at: datetime
Generated Client Schemas
Clientele converts these to Pydantic models in the client:
# In my_client/schemas.py
import pydantic
from datetime import datetime
class UserIn(pydantic.BaseModel):
username: str
email: str
age: int
class UserOut(pydantic.BaseModel):
id: int
username: str
email: str
age: int
created_at: datetime
Usage
# Create request data
user_data = schemas.UserIn(
username="alice",
email="[email protected]",
age=30
)
# Get typed response
response = client.create_user(data=user_data)
# response is typed as schemas.UserOut
print(response.id)
Path and Query Parameters
Django Ninja's path and query parameters are automatically converted to function arguments:
Django Ninja Endpoint
from ninja import Query
@api.get("/users/{user_id}")
def get_user(request, user_id: int, include_posts: bool = Query(False)):
return {
"user_id": user_id,
"include_posts": include_posts
}
Client Usage
# Required path parameter, optional query parameter
response = client.get_user_api_users_user_id_get(
user_id=123,
include_posts=True
)
Response Models
Django Ninja's response models become Pydantic schemas in the client:
Django Ninja Endpoint
class UserOut(Schema):
id: int
username: str
email: str
@api.get("/users/{user_id}", response=UserOut)
def get_user(request, user_id: int):
return UserOut(
id=user_id,
username="alice",
email="[email protected]"
)
Generated Client Usage
response = client.get_user_api_users_user_id_get(user_id=123)
# response is typed as schemas.UserOut
print(response.username)
Best Practices
- Use custom
operation_idvalues for cleaner function names - Keep schemas in sync by regenerating after API changes
- Version your generated client in git to track changes
- Test thoroughly after regenerating
- Document your endpoints - Django Ninja includes docstrings in the schema