Django REST Framework and YASG
This post explores the best practices and packages used to provide a Swagger/OpenAPI specification for an API implemented using Django REST Framework. Using Django and Django REST Framework is an excellent way to maintain a RESTful API over an Relational Database (RDBMS). Just like Django itself, it has a rich ecosystem of add-on packages. However, maintainng an OpenAPI schema specification is not an out-of-the box feature.
But why provide a Swagger/OpenAPI schema specification at all? Not all APIs need to be designed by contract, but all APIs need testing, documentation, and security. A Swagger aka OpenAPI specification is a JSON or YAML document that can tell tools how to call your API. These include API manager and gateway applications, security and other testing tools, and documentation generators/browsers. All of these tools need to know the URLs, e.g. endpoints, for the API, what HTTP methods will be supported for each URL, what parameters may be provided, and what the response format will look like.
If an API is designed by contract, the OpenAPI specification could be a hand-crafted document, maybe written using the Swagger Editor, exported to a git repository, and then served as either JSON or YAML. However, even if this is the case, we have two problems:
- We will still have multiple environments such as development, staging, QA, and production. So, the URLs returned in the document are not absolutely absolute - we have to build the absolute APIs depending on whichever environment is hosting our application. This causes the API specification to be at least a little dynamic.
- Our API is going to evolve. 90% of maintenance is actually software evolution (see Lehman’s Laws). As we maintain the API, it is going to change, and we want the API specification to match the actual API.
So, having an API specification that is generated from our actual implementation is by far the best solution. This is exactly what the designers of the Simple Object Access Protocol (SOAP) discovered, and most Web Services Description Language (WSDL) documents are written by the API application framework.
Suppose I’ve just convinced your boss, and he/she insists that the new API must have an OpenAPI specification. You google it, and discover that OpenAPI 2 and Swagger 2 are the same specification, and the latest version is OpenAPI 3. OpenAPI 3 fixes problems; the API endpoints for each environment can now be placed in one document, and the schema can express more polymorphic responses. This sounds great, but we want our API tools to work with our API tools.
Some examples include:
- SoapUI Ng Pro
- Mashery
- 3Scale
- Kong
- api.data.gov
- Amazon API Gateway
SoapUI Ng Pro is made by SmartBear Software, and since they are also the makers of that swagger editor I linked above, they are pushing version 3 hard.
However, Amazon API Gateway is probably the best API Gateway product for us. We can pay as we go, and we don’t have to manage any installation or additional acquisitiomns to use it. Amazon API Gateway only suppors OpenAPI 2. Besides, choosing a dynamically generated API specification should mean it is easy to change as the standard itself evolves.
There are a number of packages for Django that purport to generate an OpenAPI Specification. I looked at most of them, and decided that drf-yasg is the best. Let’s unpack that package name so we can remember it - “drf-yasg” is short for “Django REST Framework - Yet Another Schema Generator”. DRF is a common acronym for “Django REST Framework”, and “yasg” can be understood by remembering that the compiler tool “yacc” is an acronym for “Yet Another Compiler Compiler”.
drf-yasg has the following advantages over some of the others:
- Plain old DRF
APIView
endpoints are included by default - DRF’s own schema browser only includes DRF “ViewSets”, meaning less flexibility, and earlier Swagger schema generators leverage that. - Extensions can be made by wrapping endpoints and their actions with a decorator that tells the schema generator what to do.
So, how do we do it?
- First, we update our
requirements.txt
file to list our new dependencies:
django-rest-framework==
drf-yasg==
ruamel.yaml==
- We add the applications to
INSTALLED_APPS
in our base settings file:
INSTALLED_APPS = (
...
'rest_framework',
'drf_yasg',
)
- We add a
schema.py
file to the Django app containing our API, and place the API schema details there:
from drf_yasg import openapi
info = openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@snippets.local"),
license=openapi.License(name="BSD License"),
),
- We again update our base settings to add the location of this base schema info.:
- We add a schema_view to our root
urls.py
or our app specificurls.py
:
from drf_yasg.views import get_schema_view
...
- At this point we are done, and our swagger endpoint will exist and have something in it.