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:
- Setting up a simple Django REST API with
djangorestframework
- Integrating
drf-spectacular
- Creating the OpenAPI document to describe the API
- Customizing the OpenAPI schema
- Using the Speakeasy CLI to create an SDK based on the schema
- 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:
Terminalpip install django -
Django REST Framework
You can install Django REST Framework using the following command:
Terminalpip install djangorestframework
Example Django REST API repository
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:
from django.db import modelsclass 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
:
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:
from pathlib import PathBASE_DIR = Path(__file__).resolve().parent.parentSECRET_KEY = '<SECRET_KEY>'DEBUG = TrueALLOWED_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 = TrueUSE_TZ = TrueSTATIC_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:
python manage.py makemigrationspython manage.py migrate
Run the development server:
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:
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: 3.1.0info:title: Books APIversion: 0.1.0x-speakeasy-retries:strategy: backoffbackoff:initialInterval: 500maxInterval: 60000maxElapsedTime: 3600000exponent: 1.5statusCodes:- 5XXretryConnectionErrors: truedescription: Your bookish project descriptionpaths:/api/books/:get:operationId: api_books_listdescription: Get a list of all books available in the library with optionalfiltering and pagination.summary: Retrieve a list of bookstags:- Bookssecurity:- cookieAuth: []- basicAuth: []- {}responses:'200':content:application/json:schema:type: arrayitems:$ref: '#/components/schemas/Book'description: ''post:operationId: api_books_createdescription: Add a new book to the library collection by providing the necessarydetails.summary: Create a new booktags:- BooksrequestBody: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: truesecurity:- cookieAuth: []- basicAuth: []- {}responses:'201':content:application/json:schema:$ref: '#/components/schemas/Book'description: ''/api/books/{id}/:get:operationId: api_books_retrievedescription: Get the details of a specific book by its ID.summary: Retrieve a specific bookparameters:- in: pathname: idschema:type: integerdescription: A unique integer value identifying this book.required: truetags:- Bookssecurity:- cookieAuth: []- basicAuth: []- {}responses:'200':content:application/json:schema:$ref: '#/components/schemas/Book'description: ''put:operationId: api_books_updateparameters:- in: pathname: idschema:type: integerdescription: A unique integer value identifying this book.required: truetags:- apirequestBody: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: truesecurity:- cookieAuth: []- basicAuth: []- {}responses:'200':content:application/json:schema:$ref: '#/components/schemas/Book'description: ''patch:operationId: api_books_partial_updateparameters:- in: pathname: idschema:type: integerdescription: A unique integer value identifying this book.required: truetags:- apirequestBody: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_destroyparameters:- in: pathname: idschema:type: integerdescription: A unique integer value identifying this book.required: truetags:- apisecurity:- cookieAuth: []- basicAuth: []- {}responses:'204':description: No response body/api/books/{id}/author_books/:get:operationId: api_books_author_books_listdescription: Retrieve all books written by the same author as the specifiedbook.summary: Get all books by the same authorparameters:- in: queryname: authorschema:type: stringdescription: Filter books by author- in: pathname: idschema:type: integerdescription: A unique integer value identifying this book.required: true- in: queryname: published_dateschema:type: stringdescription: Filter books by published datetags:- Books- Authorssecurity:- cookieAuth: []- basicAuth: []- {}responses:'200':content:application/json:schema:type: arrayitems:$ref: '#/components/schemas/Book'examples:ExampleBook:value:- title: A Tale of Two Citiesauthor: Charles Dickenspublished_date: '1859-04-30'summary: Example of a book objectdescription: This example demonstrates how to use the Book APIdescription: ''x-speakeasy-retries:strategy: backoffbackoff:initialInterval: 500maxInterval: 60000maxElapsedTime: 3600000exponent: 1.5statusCodes:- 5XXretryConnectionErrors: truecomponents:schemas:Book:type: objectproperties:id:type: integerreadOnly: truetitle:type: stringmaxLength: 100author:type: stringmaxLength: 100published_date:type: stringformat: daterequired:- author- id- published_date- titlePatchedBook:type: objectproperties:id:type: integerreadOnly: truetitle:type: stringmaxLength: 100author:type: stringmaxLength: 100published_date:type: stringformat: datesecuritySchemes:basicAuth:type: httpscheme: basiccookieAuth:type: apiKeyin: cookiename: sessionidservers:- 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:
from drf_spectacular.utils import extend_schema, extend_schema_view, extend_schema_field, OpenApiExample, OpenApiParameterfrom rest_framework import viewsetsfrom rest_framework.response import Responsefrom rest_framework.decorators import actionfrom django.shortcuts import get_object_or_404from .models import Bookfrom .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 ofdrf-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:
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:
"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""from __future__ import annotationsfrom books.types import BaseModelfrom datetime import datefrom typing_extensions import TypedDictclass BookTypedDict(TypedDict):id: inttitle: strauthor: strpublished_date: dateclass Book(BaseModel):id: inttitle: strauthor: strpublished_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.