introduce
DRF spectral is a reasonable and flexible OpenAPI 3.0 pattern generated for Django REST Framework. It can automatically help us extract the information in the interface to form the interface document, and the content is very detailed, so we don't have to worry about writing the interface document anymore
This library mainly achieves three goals
- Extract more schema information from DRF
- Provide flexibility to make schema s available in the real world (not just examples)
- Generate a schema that works well with the most popular client generators
Environmental preparation
- Python >= 3.6
- Django (2.2, 3.1, 3.2)
- Django REST Framework (3.10, 3.11, 3.12)
install
Install using the pip command
pip install drf-spectacular
Then install in settings.py_ APS installation DRF spectral
INSTALLED_APPS = [ # ALL YOUR APPS 'drf_spectacular', ]
Finally, register our spectacular AutoSchema with DRF
REST_FRAMEWORK = { # YOUR SETTINGS 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', }
DRF spectral has sound default settings, which is very easy to use out of the box. There is no need to specify any settings, but we recommend that at least some metadata be specified
SPECTACULAR_SETTINGS = { 'TITLE': 'API Interface documentation', 'DESCRIPTION': 'Project details', 'VERSION': '1.0.0', # OTHER SETTINGS }
Mode of use
We just need to add the interface address in urls.py
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView urlpatterns = [ # YOUR PATTERNS path('api/schema/', SpectacularAPIView.as_view(), name='schema'), # Optional UI: path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), # swagger interface documentation path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), # redoc interface document ]
Then we start the project and visit http://127.0.0.1:8000/api/schema/swagger-ui /, the interface document appears
We can see the TITLE, DESCRIPTION and VERSION we configured in settings.py. If you want to customize more settings, please see file
Custom interface content information
We can access the swagger interface document above, but when we click the interface, we will find that there is no content information
So we also need to use the decorator @ extend in the view view_ Schema to formulate the interface information in the interface document
Let's take a look at the decorator extend first_ Schema source code
def extend_schema( operation_id: Optional[str] = None, parameters: Optional[List[Union[OpenApiParameter, _SerializerType]]] = None, request: Any = empty, responses: Any = empty, auth: Optional[List[str]] = None, description: Optional[str] = None, summary: Optional[str] = None, deprecated: Optional[bool] = None, tags: Optional[List[str]] = None, exclude: bool = False, operation: Optional[Dict] = None, methods: Optional[List[str]] = None, versions: Optional[List[str]] = None, examples: Optional[List[OpenApiExample]] = None, extensions: Optional[Dict[str, Any]] = None, ) -> Callable[[F], F]: """ Decorator mainly for the "view" method kind. Partially or completely overrides what would be otherwise generated by drf-spectacular. :param operation_id: replaces the auto-generated operation_id. make sure there are no naming collisions. :param parameters: list of additional or replacement parameters added to the auto-discovered fields. :param responses: replaces the discovered Serializer. Takes a variety of inputs that can be used individually or combined - ``Serializer`` class - ``Serializer`` instance (e.g. ``Serializer(many=True)`` for listings) - basic types or instances of ``OpenApiTypes`` - :class:`.OpenApiResponse` for bundling any of the other choices together with either a dedicated response description and/or examples. - :class:`.PolymorphicProxySerializer` for signaling that the operation may yield data from different serializers depending on the circumstances. - ``dict`` with status codes as keys and one of the above as values. Additionally in this case, it is also possible to provide a raw schema dict as value. - ``dict`` with tuples (status_code, media_type) as keys and one of the above as values. Additionally in this case, it is also possible to provide a raw schema dict as value. :param request: replaces the discovered ``Serializer``. Takes a variety of inputs - ``Serializer`` class/instance - basic types or instances of ``OpenApiTypes`` - :class:`.PolymorphicProxySerializer` for signaling that the operation accepts a set of different types of objects. - ``dict`` with media_type as keys and one of the above as values. Additionally in this case, it is also possible to provide a raw schema dict as value. :param auth: replace discovered auth with explicit list of auth methods :param description: replaces discovered doc strings :param summary: an optional short summary of the description :param deprecated: mark operation as deprecated :param tags: override default list of tags :param exclude: set True to exclude operation from schema :param operation: manually override what auto-discovery would generate. you must provide a OpenAPI3-compliant dictionary that gets directly translated to YAML. :param methods: scope extend_schema to specific methods. matches all by default. :param versions: scope extend_schema to specific API version. matches all by default. :param examples: attach request/response examples to the operation :param extensions: specification extensions, e.g. ``x-badges``, ``x-code-samples``, etc. :return: """ if methods is not None: methods = [method.upper() for method in methods] def decorator(f): BaseSchema = ( # explicit manually set schema or previous view annotation getattr(f, 'schema', None) # previously set schema with @extend_schema on views methods or getattr(f, 'kwargs', {}).get('schema', None) # previously set schema with @extend_schema on @api_view or getattr(getattr(f, 'cls', None), 'kwargs', {}).get('schema', None) # the default or api_settings.DEFAULT_SCHEMA_CLASS ) if not inspect.isclass(BaseSchema): BaseSchema = BaseSchema.__class__ def is_in_scope(ext_schema): version, _ = ext_schema.view.determine_version( ext_schema.view.request, **ext_schema.view.kwargs ) version_scope = versions is None or version in versions method_scope = methods is None or ext_schema.method in methods return method_scope and version_scope class ExtendedSchema(BaseSchema): def get_operation(self, path, path_regex, path_prefix, method, registry): self.method = method.upper() if exclude and is_in_scope(self): return None if operation is not None and is_in_scope(self): return operation return super().get_operation(path, path_regex, path_prefix, method, registry) def get_operation_id(self): if operation_id and is_in_scope(self): return operation_id return super().get_operation_id() def get_override_parameters(self): if parameters and is_in_scope(self): return super().get_override_parameters() + parameters return super().get_override_parameters() def get_auth(self): if auth and is_in_scope(self): return auth return super().get_auth() def get_examples(self): if examples and is_in_scope(self): return super().get_examples() + examples return super().get_examples() def get_request_serializer(self): if request is not empty and is_in_scope(self): return request return super().get_request_serializer() def get_response_serializers(self): if responses is not empty and is_in_scope(self): return responses return super().get_response_serializers() def get_description(self): if description and is_in_scope(self): return description return super().get_description() def get_summary(self): if summary and is_in_scope(self): return str(summary) return super().get_summary() def is_deprecated(self): if deprecated and is_in_scope(self): return deprecated return super().is_deprecated() def get_tags(self): if tags is not None and is_in_scope(self): return tags return super().get_tags() def get_extensions(self): if extensions and is_in_scope(self): return extensions return super().get_extensions() if inspect.isclass(f): # either direct decoration of views, or unpacked @api_view from OpenApiViewExtension if operation_id is not None or operation is not None: error( f'using @extend_schema on viewset class {f.__name__} with parameters ' f'operation_id or operation will most likely result in a broken schema.' ) # reorder schema class MRO so that view method annotation takes precedence # over view class annotation. only relevant if there is a method annotation for view_method_name in get_view_method_names(view=f, schema=BaseSchema): if 'schema' not in getattr(getattr(f, view_method_name), 'kwargs', {}): continue view_method = isolate_view_method(f, view_method_name) view_method.kwargs['schema'] = type( 'ExtendedMetaSchema', (view_method.kwargs['schema'], ExtendedSchema), {} ) # persist schema on class to provide annotation to derived view methods. # the second purpose is to serve as base for view multi-annotation f.schema = ExtendedSchema() return f elif callable(f) and hasattr(f, 'cls'): # 'cls' attr signals that as_view() was called, which only applies to @api_view. # keep a "unused" schema reference at root level for multi annotation convenience. setattr(f.cls, 'kwargs', {'schema': ExtendedSchema}) # set schema on method kwargs context to emulate regular view behaviour. for method in f.cls.http_method_names: setattr(getattr(f.cls, method), 'kwargs', {'schema': ExtendedSchema}) return f elif callable(f): # custom actions have kwargs in their context, others don't. create it so our create_view # implementation can overwrite the default schema if not hasattr(f, 'kwargs'): f.kwargs = {} # this simulates what @action is actually doing. somewhere along the line in this process # the schema is picked up from kwargs and used. it's involved my dear friends. # use class instead of instance due to descriptor weakref reverse collisions f.kwargs['schema'] = ExtendedSchema return f else: return f return decorator
This decorator is mainly used for view, which generates something through DRF spectral partial or complete coverage
Let's first look at the following initialization parameters
- operation_id: a unique ID, which is basically unavailable
- Parameters: additional or replacement parameters added to the list to automatically discover fields.
- responses: replace Serializer. A variety of inputs that can be used alone or in combination are required (there are the following 7 types)
- Serializer class
- Serialize instances, such as Serializer(many=True)
- The basic type or instance of OpenApiTypes
- OpenApiResponse class
- PolymorphicProxySerializer class
- 1 dictionary, with the status code as the key and one of the above items as the value (the most commonly used format is {200, None})
- 1 dictionary, with status code as key and media as key_ Type as value
- request: replace serialization and accept various inputs
- Serializer class or instance
- OpenApiTypes basic type or instance
- PolymorphicProxySerializer class
- 1 dictionary to media_type as the key and one of the above as the value
- Auth: replace the discovered auth with an explicit list of auth methods
- description: replace the found document string
- Summary: an optional short summary description
- Deprecated: marks the operation as deprecated
- tags: overrides the default tag list
- Exclude: set to True to exclude operations from the schema
- operation: manually overwrite the content that will be generated by automatic discovery. You must provide a dictionary compatible with OpenAPI3, which can be directly translated into YAML.
- Methods: check extend_ Special methods in schema match all by default
- versions: check extend_ The special API version in the schema matches all by default
- Example: attach the request / response example to the operation
- Extensions: specification extensions