How to generate an OpenAPI document with Django and Django REST framework

OpenAPI is a tool for defining and sharing REST APIs, and Django can be paired with Django REST framework to build such APIs.

This guide walks you through generating an OpenAPI document from a Django project and using it to create SDKs with Speakeasy, covering the following steps:

  1. Setting up a simple Django REST API with djangorestframework
  2. Integrating drf-spectacular
  3. Creating the OpenAPI document to describe the API
  4. Customizing the OpenAPI schema
  5. Using the Speakeasy CLI to create an SDK based on the schema
  6. Integrating SDK creation into CI/CD workflows

Requirements

This guide assumes you have a basic understanding of Django project structure and how REST APIs work.

You will also need the following installed on your machine:

  • Python version 3.8 or higher

  • Django

    You can install Django using the following command:

    Terminal
    pip install django
  • Django REST Framework

    You can install Django REST Framework using the following command:

    Terminal
    pip install djangorestframework

Example Django REST API repository

Info Icon

Example repository

The source code for the completed example is available in the Speakeasy Django example repository (opens in a new tab).

The example repository contains all the code covered in this guide. You can clone it and follow along with the tutorial or use it as a reference to add to your own Django project.


Creating the OpenAPI document to describe an API

To better understand the process of generating an OpenAPI document with Django, let’s start by inspecting some simple CRUD endpoints for an online library, along with a Book class and a serializer for the data.

Models, serializers, and views

Find the books/models.py file in the root of the repository. Open the file to see a Book model containing a few fields with validation:


In the books/serializers.py file, there is a BookSerializer class that you can use to serialize and deserialize your Book data:


The books/views.py file contains a BookViewSet class that uses Django REST Framework’s ModelViewSet to handle all of the CRUD operations for the Book model:

This code defines a simple Django REST API with CRUD operations for a Book model. The BookViewSet class provides a way to interact with the Book model through the API. It also contains a custom action called author_books that retrieves all books by the same author.


The books/urls.py file contains a router that maps the BookViewSet to the /books endpoint:


In the books_project/urls.py file, the router is included in the main Django URL configuration:

models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
published_date = models.DateField()
def __str__(self):
return self.title

Integrate drf-spectacular

Django no longer supports the built-in OpenAPI document generation, so we’ll use the drf-spectacular package to generate the OpenAPI document.

Run the following to install drf-spectacular:

Terminal
pip install drf-spectacular

Open the books_project/settings.py file. You should see 'drf_spectacular' in the INSTALLED_APPS list:

Adding this package to the INSTALLED_APPS list enables OpenAPI document generation for your Django project.


The books_project/settings.py file also contains the REST_FRAMEWORK configuration object, which sets the schema class used to create the OpenAPI document:


Notice SPECTACULAR_SETTINGS, which contains additional settings for OpenAPI document generation. You can customize these settings to fit your project:


In the books_project/urls.py file, the new OpenAPI schema and Swagger UI endpoints are added alongside the api/ endpoint:

settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = '<SECRET_KEY>'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'books',
'drf_spectacular',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'books_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'books_project.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'Books API',
'DESCRIPTION': 'Your bookish project description',
'VERSION': '0.1.0',
'SERVE_INCLUDE_SCHEMA': False,
'OAS_VERSION': '3.1.0',
"SERVERS": [
{
"url": "http://127.0.0.1:8000/",
"description": "Development server",
},
],
# OTHER SETTINGS
'EXTENSIONS_INFO': {
'x-speakeasy-retries': {
'strategy': 'backoff',
'backoff': {
'initialInterval': 500,
'maxInterval': 60000,
'maxElapsedTime': 3600000,
'exponent': 1.5,
},
'statusCodes': ['5XX'],
'retryConnectionErrors': True,
},
},
}

Apply migrations and run the server

To inspect and interact with the OpenAPI document, you need to apply database migrations and run the development server.

Apply database migrations:

Terminal
python manage.py makemigrations
python manage.py migrate

Run the development server:

Terminal
python manage.py runserver

You can now access the API and documentation:

  • Visit http://127.0.0.1:8000/api/books/ to interact with the book API.
  • Visit http://127.0.0.1:8000/swagger/ for Swagger documentation.

OpenAPI document generation

Now that we understand our Django REST API, we can generate the OpenAPI document using drf-spectacular with the following command:

Terminal
python manage.py spectacular --file openapi.yaml

This command will generate an OpenAPI document in the openapi.yaml file.


Return to the books_project/settings.py file to see how the values in SPECTACULAR_SETTINGS influence the OpenAPI document generation:


Notice that the title, description, and versions are reflected in the generated OpenAPI document:


The server URL is also included in the OpenAPI document:


Open the books/models.py file to see the Book parameters:


These same Book parameters are reflected in the OpenAPI document:

openapi.yaml
openapi: 3.1.0
info:
title: Books API
version: 0.1.0
x-speakeasy-retries:
strategy: backoff
backoff:
initialInterval: 500
maxInterval: 60000
maxElapsedTime: 3600000
exponent: 1.5
statusCodes:
- 5XX
retryConnectionErrors: true
description: Your bookish project description
paths:
/api/books/:
get:
operationId: api_books_list
description: Get a list of all books available in the library with optional
filtering and pagination.
summary: Retrieve a list of books
tags:
- Books
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Book'
description: ''
post:
operationId: api_books_create
description: Add a new book to the library collection by providing the necessary
details.
summary: Create a new book
tags:
- Books
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Book'
multipart/form-data:
schema:
$ref: '#/components/schemas/Book'
required: true
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
/api/books/{id}/:
get:
operationId: api_books_retrieve
description: Get the details of a specific book by its ID.
summary: Retrieve a specific book
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this book.
required: true
tags:
- Books
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
put:
operationId: api_books_update
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this book.
required: true
tags:
- api
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Book'
multipart/form-data:
schema:
$ref: '#/components/schemas/Book'
required: true
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
patch:
operationId: api_books_partial_update
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this book.
required: true
tags:
- api
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedBook'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/PatchedBook'
multipart/form-data:
schema:
$ref: '#/components/schemas/PatchedBook'
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Book'
description: ''
delete:
operationId: api_books_destroy
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this book.
required: true
tags:
- api
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'204':
description: No response body
/api/books/{id}/author_books/:
get:
operationId: api_books_author_books_list
description: Retrieve all books written by the same author as the specified
book.
summary: Get all books by the same author
parameters:
- in: query
name: author
schema:
type: string
description: Filter books by author
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this book.
required: true
- in: query
name: published_date
schema:
type: string
description: Filter books by published date
tags:
- Books
- Authors
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Book'
examples:
ExampleBook:
value:
- title: A Tale of Two Cities
author: Charles Dickens
published_date: '1859-04-30'
summary: Example of a book object
description: This example demonstrates how to use the Book API
description: ''
x-speakeasy-retries:
strategy: backoff
backoff:
initialInterval: 500
maxInterval: 60000
maxElapsedTime: 3600000
exponent: 1.5
statusCodes:
- 5XX
retryConnectionErrors: true
components:
schemas:
Book:
type: object
properties:
id:
type: integer
readOnly: true
title:
type: string
maxLength: 100
author:
type: string
maxLength: 100
published_date:
type: string
format: date
required:
- author
- id
- published_date
- title
PatchedBook:
type: object
properties:
id:
type: integer
readOnly: true
title:
type: string
maxLength: 100
author:
type: string
maxLength: 100
published_date:
type: string
format: date
securitySchemes:
basicAuth:
type: http
scheme: basic
cookieAuth:
type: apiKey
in: cookie
name: sessionid
servers:
- url: http://127.0.0.1:8000/
description: Development server

OpenAPI document customization

The OpenAPI document generated by drf-spectacular may not be detailed enough for all use cases. Fortunately, it can be customized to better serve information about your API endpoints. You can add descriptions, tags, examples, and more to make the documentation more informative and user-friendly.

In the customized (opens in a new tab) branch of the example repository, you can find a customized OpenAPI document that demonstrates the available options for modifying your generated document.

The drf-spectacular package provides decorators to directly modify the schema for your views and viewsets.

  • @extend_schema_view: Allows customization of all methods in a viewset.
  • @extend_schema: Allows customization of individual methods or actions.

As an example of how you can customize the schema of the BookViewSet, open the books/views.py file and update it with the following code:


You can extend or override the document generation for specific views or viewsets using the @extend_schema_view decorator:


This results in the following additions, for example, to the /api/books/ get operation in the document:


Then, you can use the @extend_schema decorator to customize individual actions:


This results in the following additions to the get operation for author_books in the document:


You can make the OpenAPI documentation more readable and organized by specifying tags and descriptions for your viewset methods:


This adds a list of tags to the viewset methods in the OpenAPI document:


You can customize query parameters for your API endpoint. For example, you can add filtering or sorting options:


These options then reflect as follows:


You can add examples, such as an example of a book object, to assist anyone using your API:


The document is then updated with the examples:


You can add global retries to the OpenAPI document by modifying SPECTACULAR_SETTINGS in the books_project/settings.py file:


It is also possible to apply the retries setting to specific views or viewsets using the @extend_schema decorator:

views.py
from drf_spectacular.utils import extend_schema, extend_schema_view, extend_schema_field, OpenApiExample, OpenApiParameter
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
from django.shortcuts import get_object_or_404
from .models import Book
from .serializers import BookSerializer
@extend_schema_view(
list=extend_schema(
summary="Retrieve a list of books",
description="Get a list of all books available in the library with optional filtering and pagination.",
tags=["Books"]
),
retrieve=extend_schema(
summary="Retrieve a specific book",
description="Get the details of a specific book by its ID.",
tags=["Books"],
responses={200: BookSerializer}
),
create=extend_schema(
summary="Create a new book",
description="Add a new book to the library collection by providing the necessary details.",
tags=["Books"],
)
)
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
@extend_schema_field(int)
class CustomField(BookSerializer):
def to_representation(self, value):
return int(value)
@extend_schema(
summary="Get all books by the same author",
description="Retrieve all books written by the same author as the specified book.",
responses={200: BookSerializer(many=True)},
extensions={
'x-speakeasy-retries': {
'strategy': 'backoff',
'backoff': {
'initialInterval': 500,
'maxInterval': 60000,
'maxElapsedTime': 3600000,
'exponent': 1.5,
},
'statusCodes': ['5XX'],
'retryConnectionErrors': True,
},
},
tags=["Books", "Authors"],
parameters=[
OpenApiParameter(name='author', description='Filter books by author', required=False, type=str),
OpenApiParameter(name='published_date', description='Filter books by published date', required=False, type=str),
],
examples=[
OpenApiExample(
"Example Book",
summary="Example of a book object",
description="This example demonstrates how to use the Book API",
value={
"title": "A Tale of Two Cities",
"author": "Charles Dickens",
"published_date": "1859-04-30"
}
)
],
)
@action(detail=True, methods=['get'])
def author_books(self, request, pk=None):
book = get_object_or_404(Book, pk=pk)
books_by_author = Book.objects.filter(author=book.author)
serializer = self.get_serializer(books_by_author, many=True)
return Response(serializer.data)

In summary, the drf-spectacular package provides a variety of ways to customize the OpenAPI document for your Django REST API. You can use decorators, tags, descriptions, parameters, fields, examples, and global settings to modify the document according to your requirements.

  • Decorators (@extend_schema and @extend_schema_view): Customize individual methods or entire views.
  • Tags and descriptions: Organize endpoints for better readability.
  • Parameters: Define custom parameters using OpenApiParameter.
  • OpenAPI components: Use OpenApiExample to provide reusable components or examples.
  • Global settings (SPECTACULAR_SETTINGS): Modify the global behavior of drf-spectacular.

For more information about customizing the OpenAPI schema with drf-spectacular, refer to the official drf-spectacular documentation (opens in a new tab).

Creating SDKs for a Django REST API

To create a Python SDK for the Django REST API, run the following command:

Terminal
speakeasy quickstart

Follow the onscreen prompts to provide the configuration details for your new SDK, such as the name, schema location, and output path. When prompted, enter openapi.yaml for the OpenAPI document location, select a language, and generate.

Add SDK generation to your GitHub Actions

The Speakeasy sdk-generation-action (opens in a new tab) repository provides workflows for integrating the Speakeasy CLI into your CI/CD pipeline, so that your SDKs are recreated whenever your OpenAPI document changes.

You can set up Speakeasy to automatically push a new branch to your SDK repositories for your engineers to review before merging the SDK changes.

For an overview of how to set up automation for your SDKs, see the Speakeasy SDK Generation Action and Workflows (opens in a new tab) documentation.

SDK customization

Explore the effects of your newly generated OpenAPI document on the SDK created by Speakeasy.

After creating your SDK with Speakeasy, you will find a new directory containing the generated SDK code. Let’s explore this code a bit further.

These examples assume a Python SDK named books-python was generated from the example Django project above. Edit any paths to reflect your environment if you want to follow in your own project.

Navigate into the books-python/src/books directory and open the book.py file created by Speakeasy. Note how the OpenAPI document was used to create the Book class:


Open the api.py file to see the methods that call the web API from an application using the SDK:


Notice the server_url parameter in the api.py file, which was configured in the SERVERS key under SPECTACULAR_SETTINGS in the settings.py file:


And the retries parameter:


This is all used to build the request to the API endpoint:


Finally, you should get the result of the global retries strategy set up in SPECTACULAR_SETTINGS in the settings.py file:


You can also find a method implementing the retries strategy:

book.py
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
from __future__ import annotations
from books.types import BaseModel
from datetime import date
from typing_extensions import TypedDict
class BookTypedDict(TypedDict):
id: int
title: str
author: str
published_date: date
class Book(BaseModel):
id: int
title: str
author: str
published_date: date

Summary

In this guide, we showed you how to generate an OpenAPI document for a Django API and use Speakeasy to create an SDK based on the OpenAPI document. The step-by-step instructions included adding relevant tools to the Django project, generating an OpenAPI document, enhancing it for improved creation, using Speakeasy OpenAPI extensions, and interpreting the basics of the generated SDK.

We also explored automating SDK generation through CI/CD workflows and improving API operations.