If the default spec generation does not quite match what you were hoping to achieve, drf-yasg
provides some custom behavior hooks by default.
This library generates OpenAPI 2.0 documents. The authoritative specification for this document's structure will always be the official documentation over at swagger.io and the OpenAPI 2.0 specification page.
Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification is structured, starting from the root Swagger
object.
.Swagger
objectinfo
,schemes
,securityDefinitions
and other informative attributespaths
:.Paths
objectA list of all the paths in the API in the form of a mapping
{path}
:.PathItem
- each.PathItem
has multiple operations keyed by method{http_method}
:.Operation
Each operation is thus uniquely identified by its
(path, http_method)
combination, e.g.GET /articles/
,POST /articles/
, etc.
parameters
: [.Parameter
] - and a list of path parameters
definitions
: named ModelsA list of all the named models in the API in the form of a mapping
{ModelName}
:.Schema
.Operation
contains the following information about each operation:parameters
: [.Parameter
]A list of all the query, header and form parameters accepted by the operation.
- there can also be at most one body parameter whose structure is represented by a
.Schema
or a reference to one (.SchemaRef
)
- there can also be at most one body parameter whose structure is represented by a
responses
:.Responses
A list of all the possible responses the operation is expected to return. Each response can optionally have a
.Schema
which describes the structure of its body.{status_code}
:.Response
- mapping of status code to response definition
operationId
- should be unique across all operationstags
- used to group operations in the listing
It is interesting to note the main differences between .Parameter
and .Schema
objects:
.Schema |
.Parameter |
---|---|
Can nest other Schemas | Cannot nest other Parameters Can only nest a Schema if the parameter is in: body |
Cannot describe file uploads - file is not permitted as a value for type |
Can describe file uploads via type = file , but only as part of a form .Operation 1 |
Can be used in .Response s |
Cannot be used in .Response s |
Cannot be used in form .Operation s2 |
Can be used in form .Operation s3 |
Can only describe request or response bodies | Can describe query , form , header or path parameters |
This section describes where information is sourced from when using the default generation process.
.Paths
are generated by exploring the patterns registered in your defaulturlconf
, or thepatterns
andurlconf
you specified when constructing.OpenAPISchemaGenerator
; only views inheriting from Django Rest Framework'sAPIView
are looked at, all other views are ignoredpath
.Parameter
s are generated by looking in the URL pattern for any template parameters; attempts are made to guess their type from the viewsqueryset
andlookup_field
, if applicable. You can override path parameters viamanual_parameters
in@swagger_auto_schema <custom-spec-swagger-auto-schema>
.query
.Parameter
s - i.e. parameters specified in the URL as/path/?query1=value&query2=value
-are generated from your view'sfilter_backends
andpaginator
, if any are declared. Additional parameters can be specified via thequery_serializer
andmanual_parameters
arguments of@swagger_auto_schema <custom-spec-swagger-auto-schema>
The request body is only generated for the HTTP
POST
,PUT
andPATCH
methods, and is sourced from the view'sserializer_class
. You can also override the request body using therequest_body
argument of@swagger_auto_schema <custom-spec-swagger-auto-schema>
.- if the view represents a form request (that is, all its parsers are of the
multipart/form-data
orapplication/x-www-form-urlencoded
media types), the request body will be output asform
.Parameter
s - if it is not a form request, the request body will be output as a single
body
.Parameter
wrapped around a.Schema
- if the view represents a form request (that is, all its parsers are of the
header
.Parameter
s are supported by the OpenAPI specification but are never generated by this library; you can still add them usingmanual_parameters
..Responses
are generated as follows:- if
responses
is provided to@swagger_auto_schema <custom-spec-swagger-auto-schema>
and contains at least one success status code (i.e. any 2xx status code), no automatic response is generated and the given response is used as described in the@swagger_auto_schema documentation <.swagger_auto_schema>
otherwise, an attempt is made to generate a default response:
- the success status code is assumed to be
204` for
DELETErequests,
201for
POSTrequests, and
200`` for all other request methods - if the view has a request body, the same
Serializer
or.Schema
as in the request body is used in generating the.Response
schema; this is inline with the defaultGenericAPIView
andGenericViewSet
behavior - if the view has no request body, its
serializer_class
is used to generate the.Response
schema - if the view is a list view (as defined by
.is_list_view
), the response schema is wrapped in an array - if the view is also paginated, the response schema is then wrapped in the appropriate paging response structure
- the description of the response is left blank
- the success status code is assumed to be
- if
.Response
headers are supported by the OpenAPI specification but not currently supported by this library; you can still add them manually by providing an appropriately structured dictionary to theheaders
property of a.Response
object- descriptions for
.Operation
s,.Parameter
s and.Schema
s are picked up from docstrings andhelp_text
attributes in the same manner as the default DRF SchemaGenerator
You can use the @swagger_auto_schema <.swagger_auto_schema>
decorator on view functions to override some properties of the generated .Operation
. For example, in a ViewSet
,
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
def partial_update(self, request, *args, **kwargs):
"""partial_update method docstring"""
...
will override the description of the PATCH /article/{id}/
operation, and document a 404 response with no body and the given description.
Where you can use the @swagger_auto_schema <.swagger_auto_schema>
decorator depends on the type of your view:
for function based
@api_view
s, because the same view can handle multiple methods, and thus represent multiple operations, you have to add the decorator multiple times if you want to override different operations:test_param = openapi.Parameter('test', openapi.IN_QUERY, description="test manual param", type=openapi.TYPE_BOOLEAN) user_response = openapi.Response('response description', UserSerializer) # 'method' can be used to customize a single HTTP method of a view @swagger_auto_schema(method='get', manual_parameters=[test_param], responses={200: user_response}) # 'methods' can be used to apply the same modification to multiple methods @swagger_auto_schema(methods=['put', 'post'], request_body=UserSerializer) @api_view(['GET', 'PUT', 'POST']) def user_detail(request, pk): ...
for class based
APIView
,GenericAPIView
and non-ViewSet
derivatives, you have to decorate the respective method of each operation:class UserList(APIView): @swagger_auto_schema(responses={200: UserSerializer(many=True)}) def get(self, request): ... @swagger_auto_schema(operation_description="description") def post(self, request): ...
for
ViewSet
,GenericViewSet
,ModelViewSet
, because each viewset corresponds to multiple paths, you have to decorate the action methods, i.e.list
,create
,retrieve
, etc. Additionally,@list_route
s or@detail_route
s defined on the viewset, like function based api views, can respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:class ArticleViewSet(viewsets.ModelViewSet): # method or 'methods' can be skipped because the list_route only handles a single method (GET) @swagger_auto_schema(operation_description='GET /articles/today/') @list_route(methods=['get']) def today(self, request): ... @swagger_auto_schema(method='get', operation_description="GET /articles/{id}/image/") @swagger_auto_schema(method='post', operation_description="POST /articles/{id}/image/") @detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,)) def image(self, request, id=None): ... @swagger_auto_schema(operation_description="PUT /articles/{id}/") def update(self, request, *args, **kwargs): ... @swagger_auto_schema(operation_description="PATCH /articles/{id}/") def partial_update(self, request, *args, **kwargs): ...
Tip
If you want to customize the generation of a method you are not implementing yourself, you can use swagger_auto_schema
in combination with Django's method_decorator
:
@method_decorator(name='list', decorator=swagger_auto_schema(
operation_description="description from swagger_auto_schema via method_decorator"
))
class ArticleViewSet(viewsets.ModelViewSet):
...
This allows you to avoid unnecessarily overriding the method.
Tip
You can go even further and directly decorate the result of as_view
, in the same manner you would override an @api_view
as described above:
decorated_login_view = \
swagger_auto_schema(
method='post',
responses={status.HTTP_200_OK: LoginResponseSerializer}
)(LoginView.as_view())
urlpatterns = [
...
url(r'^login/$', decorated_login_view, name='login')
]
This can allow you to avoid skipping an unnecessary subclass altogether.
Warning
However, do note that both of the methods above can lead to unexpected (and maybe surprising) results by replacing/decorating methods on the base class itself.
You can define some per-serializer options by adding a Meta
class to your serializer, e.g.:
class WhateverSerializer(Serializer):
...
class Meta:
... options here ...
Currently, the only option you can add here is
ref_name
- a string which will be used as the model definition name for this serializer class; setting it toNone
will force the serializer to be generated as an inline model everywhere it is used
For more advanced control you can subclass ~.inspectors.SwaggerAutoSchema
- see the documentation page for a list of methods you can override.
You can put your custom subclass to use by setting it on a view method using the @swagger_auto_schema <custom-spec-swagger-auto-schema>
decorator described above, by setting it as a class-level attribute named swagger_schema
on the view class, or globally via settings <default-class-settings>
.
For example, to generate all operation IDs as camel case, you could do:
from inflection import camelize
class CamelCaseOperationIDAutoSchema(SwaggerAutoSchema):
def get_operation_id(self, operation_keys):
operation_id = super(CamelCaseOperationIDAutoSchema, self).get_operation_id(operation_keys)
return camelize(operation_id, uppercase_first_letter=False)
SWAGGER_SETTINGS = {
'DEFAULT_AUTO_SCHEMA_CLASS': 'path.to.CamelCaseOperationIDAutoSchema',
...
}
If you need to control things at a higher level than .Operation
objects (e.g. overall document structure, vendor extensions in metadata) you can also subclass .OpenAPISchemaGenerator
- again, see the documentation page for a list of its methods.
This custom generator can be put to use by setting it as the .generator_class
of a .SchemaView
using .get_schema_view
.
1.1
For customizing behavior related to specific field, serializer, filter or paginator classes you can implement the ~.inspectors.FieldInspector
, ~.inspectors.SerializerInspector
, ~.inspectors.FilterInspector
, ~.inspectors.PaginatorInspector
classes and use them with @swagger_auto_schema <custom-spec-swagger-auto-schema>
or one of the related settings <default-class-settings>
.
A ~.inspectors.FilterInspector
that adds a description to all DjangoFilterBackend
parameters could be implemented like so:
class DjangoFilterDescriptionInspector(CoreAPICompatInspector):
def get_filter_parameters(self, filter_backend):
if isinstance(filter_backend, DjangoFilterBackend):
result = super(DjangoFilterDescriptionInspector, self).get_filter_parameters(filter_backend)
for param in result:
if not param.get('description', ''):
param.description = "Filter the returned list by {field_name}".format(field_name=param.name)
return result
return NotHandled
@method_decorator(name='list', decorator=swagger_auto_schema(
filter_inspectors=[DjangoFilterDescriptionInspector]
))
class ArticleViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend,)
filter_fields = ('title',)
...
A second example, of a ~.inspectors.FieldInspector
that removes the title
attribute from all generated .Schema
objects:
class NoSchemaTitleInspector(FieldInspector):
def process_result(self, result, method_name, obj, **kwargs):
# remove the `title` attribute of all Schema objects
if isinstance(result, openapi.Schema.OR_REF):
# traverse any references and alter the Schema object in place
schema = openapi.resolve_ref(result, self.components)
schema.pop('title', None)
# no ``return schema`` here, because it would mean we always generate
# an inline `object` instead of a definition reference
# return back the same object that we got - i.e. a reference if we got a reference
return result
class NoTitleAutoSchema(SwaggerAutoSchema):
field_inspectors = [NoSchemaTitleInspector] + swagger_settings.DEFAULT_FIELD_INSPECTORS
class ArticleViewSet(viewsets.ModelViewSet):
swagger_schema = NoTitleAutoSchema
...
Note
A note on references - .Schema
objects are sometimes output by reference (.SchemaRef
); in fact, that is how named models are implemented in OpenAPI:
- in the output swagger document there is a
definitions
section containing.Schema
objects for all models- every usage of a model refers to that single
.Schema
object - for example, in the ArticleViewSet above, all requests and responses containg anArticle
model would refer to the same schema definition by a'$ref': '#/definitions/Article'
This is implemented by only generating one .Schema
object for every serializer class encountered.
This means that you should generally avoid view or method-specific FieldInspector
s if you are dealing with references (a.k.a named models), because you can never know which view will be the first to generate the schema for a given serializer.
a form Operation is an
.Operation
that consumesmultipart/form-data
orapplication/x-www-form-urlencoded
content- a form Operation cannot have
body
parameters - a non-form operation cannot have
form
parameters
- a form Operation cannot have
a form Operation is an
.Operation
that consumesmultipart/form-data
orapplication/x-www-form-urlencoded
content- a form Operation cannot have
body
parameters - a non-form operation cannot have
form
parameters
- a form Operation cannot have
a form Operation is an
.Operation
that consumesmultipart/form-data
orapplication/x-www-form-urlencoded
content- a form Operation cannot have
body
parameters - a non-form operation cannot have
form
parameters
- a form Operation cannot have