Using Clientele with Django REST Framework
This guide shows you how to generate a Python client for a Django REST Framework (DRF) API using Clientele and drf-spectacular.
Prerequisites
- A Django REST Framework application
- drf-spectacular installed and configured
Why drf-spectacular?
Django REST Framework doesn't include built-in OpenAPI schema generation. drf-spectacular is the recommended solution for generating OpenAPI 3.0 schemas from DRF applications. It's actively maintained and provides excellent OpenAPI support.
Step 1: Install and Configure drf-spectacular
If you haven't already set up drf-spectacular, follow these steps:
Install drf-spectacular
pip install drf-spectacular
Configure Django Settings
Add to your settings.py:
INSTALLED_APPS = [
# ...
'rest_framework',
'drf_spectacular',
# ...
]
REST_FRAMEWORK = {
# ...
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'Your API',
'DESCRIPTION': 'Your API description',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
Add URL Patterns
In your urls.py:
from drf_spectacular.views import SpectacularAPIView
from django.urls import path
urlpatterns = [
# ...
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
]
Step 2: Get Your OpenAPI Schema
After configuring drf-spectacular, your OpenAPI schema is available at:
http://your-api-domain/api/schema/
For local development:
http://localhost:8000/api/schema/
Download the Schema
Option A: Use the URL directly:
clientele generate -u http://localhost:8000/api/schema/ -o my_client/
Option B: Download the schema file:
curl http://localhost:8000/api/schema/ > openapi.json
clientele generate -f openapi.json -o my_client/
Option C: Generate schema file with Django management command:
python manage.py spectacular --file openapi.json
clientele generate -f openapi.json -o my_client/
Step 3: Generate the Client
Basic Generation
Generate a function-based client:
clientele generate -u http://localhost:8000/api/schema/ -o my_client/
Class-Based Client
For an object-oriented approach:
clientele generate-class -u http://localhost:8000/api/schema/ -o my_client/
Async Client
Generate an async client (note: DRF itself is synchronous, but the client can be async):
clientele generate -u http://localhost:8000/api/schema/ -o my_client/ --asyncio t
Step 4: Use the Generated Client
Function-Based Client Example
from my_client import client, schemas
# List resources
response = client.list_users_api_users_get()
# Create a resource
user_data = schemas.UserRequest(
username="alice",
email="alice@example.com"
)
response = client.create_user_api_users_post(data=user_data)
# Handle responses
match response:
case schemas.User():
print(f"User created: {response.username}")
case schemas.ValidationError():
print(f"Validation failed: {response.detail}")
Improving operationId in DRF
By default, drf-spectacular generates operation IDs from view names and paths. You can customize them for cleaner function names.
Using extend_schema Decorator
from drf_spectacular.utils import extend_schema
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@extend_schema(operation_id="list_users")
def list(self, request):
return super().list(request)
@extend_schema(operation_id="create_user")
def create(self, request):
return super().create(request)
@extend_schema(operation_id="get_user")
def retrieve(self, request, pk=None):
return super().retrieve(request, pk)
This generates:
client.list_users()instead of a generic operation IDclient.create_user()instead of verbose auto-generated namesclient.get_user()for clean, readable function names
For Function-Based Views
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import api_view
@extend_schema(
operation_id="get_user_stats",
responses={200: UserStatsSerializer}
)
@api_view(['GET'])
def user_stats(request, user_id):
# ...
return Response(data)
Authentication
Token Authentication
If your DRF API uses token authentication:
# Django settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authtoken.models.TokenAuthentication',
],
}
Configure in the client's config.py:
def get_bearer_token() -> str:
"""
Provide your DRF auth token.
"""
from os import environ
return environ.get("DRF_AUTH_TOKEN", "your-token-here")
Session Authentication
For session-based auth, you may need to handle cookie management manually in the client's config.py using additional headers.
JWT Authentication
If using JWT (e.g., with djangorestframework-simplejwt):
def get_bearer_token() -> str:
"""
Provide your JWT access token.
"""
from os import environ
return environ.get("JWT_ACCESS_TOKEN", "your-jwt-token")
Regenerating the Client
When your DRF API changes, regenerate the client:
clientele generate -u http://localhost:8000/api/schema/ -o my_client/ --regen t
Recommended Workflow
- Update your DRF serializers/views
- Run migrations if needed
- Regenerate the schema:
python manage.py spectacular --file openapi.json - Regenerate the client:
clientele generate -f openapi.json -o my_client/ --regen t - Review changes:
git diff - Test the updated client
- Commit changes
Serializers and Schemas
DRF serializers become Pydantic models in the generated client:
DRF Serializer
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'is_active']
Generated Pydantic Schema
# In my_client/schemas.py
class User(pydantic.BaseModel):
id: int
username: str
email: str
is_active: bool
Usage
# The client returns properly typed responses
response = client.get_user_api_users_id_get(id=123)
# response is typed as schemas.User
assert isinstance(response, schemas.User)
print(response.username) # Full IDE support
ViewSets and Routers
DRF ViewSets with routers automatically generate appropriate endpoints:
DRF ViewSet
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Router Configuration
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet)
This generates endpoints like:
GET /users/→client.list_users()POST /users/→client.create_user(data=...)GET /users/{id}/→client.get_user(id=...)PUT /users/{id}/→client.update_user(id=..., data=...)DELETE /users/{id}/→client.delete_user(id=...)
Pagination
DRF pagination is reflected in the schema and client:
DRF Pagination Setup
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100
}
Generated Schema
The response will include pagination fields:
response = client.list_users_api_users_get(page=2)
# Response has structure like:
# {
# "count": 250,
# "next": "http://...",
# "previous": "http://...",
# "results": [...]
# }
Filtering and Query Parameters
Query parameters are automatically converted to function arguments:
DRF View with Filtering
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filterset_fields = ['is_active', 'username']
Client Usage
# Query parameters become function arguments
response = client.list_users_api_users_get(
is_active=True,
username="alice"
)
Known Limitations
OpenAPI Version
- Supported: drf-spectacular generates OpenAPI 3.0 schemas that work perfectly with Clientele
- Partial Support: Some complex DRF features may need schema customization
- Not Supported: DRF's older CoreAPI schema format is not supported - use drf-spectacular
Complex Serializers
- Most DRF serializer features work out of the box
- Nested serializers are fully supported
- SerializerMethodField may require manual schema hints with
@extend_schema_field
File Uploads
File upload endpoints may require manual customization in the generated client.
Best Practices
- Use
@extend_schemato provide clear operation IDs and response types - Document your serializers - drf-spectacular uses docstrings in schema generation
- Regenerate regularly to keep client in sync with API changes
- Use
extend_schema_fieldfor SerializerMethodFields to ensure proper typing - Version your generated client in git
Helpful drf-spectacular Features
Documenting Responses
from drf_spectacular.utils import extend_schema, OpenApiResponse
@extend_schema(
responses={
200: UserSerializer,
400: OpenApiResponse(description='Invalid request'),
404: OpenApiResponse(description='User not found'),
}
)
@api_view(['GET'])
def get_user(request, user_id):
# ...
Documenting Request Bodies
@extend_schema(
request=UserSerializer,
responses={201: UserSerializer}
)
@api_view(['POST'])
def create_user(request):
# ...