mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 12:30:11 +03:00
Refactor to ViewInspector
plus AutoSchema
The interface then is **just** `get_link()`
This commit is contained in:
parent
81bac6fe51
commit
416e57f7b8
|
@ -10,7 +10,14 @@ API schemas are a useful tool that allow for a range of use cases, including
|
||||||
generating reference documentation, or driving dynamic client libraries that
|
generating reference documentation, or driving dynamic client libraries that
|
||||||
can interact with your API.
|
can interact with your API.
|
||||||
|
|
||||||
## Representing schemas internally
|
## Install Core API
|
||||||
|
|
||||||
|
You'll need to install the `coreapi` package in order to add schema support
|
||||||
|
for REST framework.
|
||||||
|
|
||||||
|
pip install coreapi
|
||||||
|
|
||||||
|
## Internal schema representation
|
||||||
|
|
||||||
REST framework uses [Core API][coreapi] in order to model schema information in
|
REST framework uses [Core API][coreapi] in order to model schema information in
|
||||||
a format-independent representation. This information can then be rendered
|
a format-independent representation. This information can then be rendered
|
||||||
|
@ -68,9 +75,34 @@ has to be rendered into the actual bytes that are used in the response.
|
||||||
REST framework includes a renderer class for handling this media type, which
|
REST framework includes a renderer class for handling this media type, which
|
||||||
is available as `renderers.CoreJSONRenderer`.
|
is available as `renderers.CoreJSONRenderer`.
|
||||||
|
|
||||||
|
### Alternate schema formats
|
||||||
|
|
||||||
Other schema formats such as [Open API][open-api] ("Swagger"),
|
Other schema formats such as [Open API][open-api] ("Swagger"),
|
||||||
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can
|
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can also
|
||||||
also be supported by implementing a custom renderer class.
|
be supported by implementing a custom renderer class that handles converting a
|
||||||
|
`Document` instance into a bytestring representation.
|
||||||
|
|
||||||
|
If there is a Core API codec package that supports encoding into the format you
|
||||||
|
want to use then implementing the renderer class can be done by using the codec.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
For example, the `openapi_codec` package provides support for encoding or decoding
|
||||||
|
to the Open API ("Swagger") format:
|
||||||
|
|
||||||
|
from rest_framework import renderers
|
||||||
|
from openapi_codec import OpenAPICodec
|
||||||
|
|
||||||
|
class SwaggerRenderer(renderers.BaseRenderer):
|
||||||
|
media_type = 'application/openapi+json'
|
||||||
|
format = 'swagger'
|
||||||
|
|
||||||
|
def render(self, data, media_type=None, renderer_context=None):
|
||||||
|
codec = OpenAPICodec()
|
||||||
|
return codec.dump(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Schemas vs Hypermedia
|
## Schemas vs Hypermedia
|
||||||
|
|
||||||
|
@ -89,18 +121,111 @@ document, detailing both the current state and the available interactions.
|
||||||
Further information and support on building Hypermedia APIs with REST framework
|
Further information and support on building Hypermedia APIs with REST framework
|
||||||
is planned for a future version.
|
is planned for a future version.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Adding a schema
|
# Creating a schema
|
||||||
|
|
||||||
You'll need to install the `coreapi` package in order to add schema support
|
|
||||||
for REST framework.
|
|
||||||
|
|
||||||
pip install coreapi
|
|
||||||
|
|
||||||
REST framework includes functionality for auto-generating a schema,
|
REST framework includes functionality for auto-generating a schema,
|
||||||
or allows you to specify one explicitly. There are a few different ways to
|
or allows you to specify one explicitly.
|
||||||
add a schema to your API, depending on exactly what you need.
|
|
||||||
|
## Manual Schema Specification
|
||||||
|
|
||||||
|
To manually specify a schema you create a Core API `Document`, similar to the
|
||||||
|
example above.
|
||||||
|
|
||||||
|
schema = coreapi.Document(
|
||||||
|
title='Flight Search API',
|
||||||
|
content={
|
||||||
|
...
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Automatic Schema Generation
|
||||||
|
|
||||||
|
Automatic schema generation is provided by the `SchemaGenerator` class.
|
||||||
|
|
||||||
|
`SchemaGenerator` processes a list of routed URL pattterns and compiles the
|
||||||
|
appropriately structured Core API Document.
|
||||||
|
|
||||||
|
Basic usage is just to provide the title for your schema and call
|
||||||
|
`get_schema()`:
|
||||||
|
|
||||||
|
generator = schemas.SchemaGenerator(title='Flight Search API')
|
||||||
|
schema = generator.get_schema()
|
||||||
|
|
||||||
|
### Per-View Schema Customisation
|
||||||
|
|
||||||
|
By default, view introspection is performed by an `AutoSchema` instance
|
||||||
|
accessible via the `schema` attribute on `APIView`. This provides the
|
||||||
|
appropriate Core API `Link` object for the view, request method and path:
|
||||||
|
|
||||||
|
auto_schema = view.schema
|
||||||
|
coreapi_link = auto_schema.get_link(...)
|
||||||
|
|
||||||
|
|
||||||
|
(Aside: In compiling the schema, `SchemaGenerator` calls `view.schema.get_link()` for
|
||||||
|
each view, allowed method and path.)
|
||||||
|
|
||||||
|
To customise the `Link` generation you may:
|
||||||
|
|
||||||
|
* Instantiate `AutoSchema` on your view with the `manual_fields` kwarg:
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.schemas import AutoSchema
|
||||||
|
|
||||||
|
class CustomView(APIView):
|
||||||
|
...
|
||||||
|
schema = AutoSchema(
|
||||||
|
manual_fields= {
|
||||||
|
"extra_field": coreapi.Field(...)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
This allows extension for the most common case without subclassing.
|
||||||
|
|
||||||
|
* Provide an `AutoSchema` subclass with more complex customisation:
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.schemas import AutoSchema
|
||||||
|
|
||||||
|
class CustomSchema(AutoSchema):
|
||||||
|
def get_link(...):
|
||||||
|
# Implemet custom introspection here (or in other sub-methods)
|
||||||
|
|
||||||
|
class CustomView(APIView):
|
||||||
|
...
|
||||||
|
schema = CustomSchema()
|
||||||
|
|
||||||
|
This provides complete control over view introspection.
|
||||||
|
|
||||||
|
* Instantiate `ManualSchema` on your view, providing the Core API `Link`
|
||||||
|
explicitly:
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.schemas import ManualSchema
|
||||||
|
|
||||||
|
class CustomView(APIView):
|
||||||
|
...
|
||||||
|
schema = ManualSchema(
|
||||||
|
coreapi.Link(...)
|
||||||
|
)
|
||||||
|
|
||||||
|
This allows manually specifying the schema for some views whilst maintaining
|
||||||
|
automatic generation elsewhere.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: For full details on `SchemaGenerator` plus the `AutoSchema` and
|
||||||
|
`ManualSchema` descriptors see the [API Reference below](#api-reference).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Adding a schema view
|
||||||
|
|
||||||
|
There are a few different ways to add a schema view to your API, depending on
|
||||||
|
exactly what you need.
|
||||||
|
|
||||||
## The get_schema_view shortcut
|
## The get_schema_view shortcut
|
||||||
|
|
||||||
|
@ -342,32 +467,6 @@ A generic viewset with sections in the class docstring, using multi-line style.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alternate schema formats
|
|
||||||
|
|
||||||
In order to support an alternate schema format, you need to implement a custom renderer
|
|
||||||
class that handles converting a `Document` instance into a bytestring representation.
|
|
||||||
|
|
||||||
If there is a Core API codec package that supports encoding into the format you
|
|
||||||
want to use then implementing the renderer class can be done by using the codec.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
For example, the `openapi_codec` package provides support for encoding or decoding
|
|
||||||
to the Open API ("Swagger") format:
|
|
||||||
|
|
||||||
from rest_framework import renderers
|
|
||||||
from openapi_codec import OpenAPICodec
|
|
||||||
|
|
||||||
class SwaggerRenderer(renderers.BaseRenderer):
|
|
||||||
media_type = 'application/openapi+json'
|
|
||||||
format = 'swagger'
|
|
||||||
|
|
||||||
def render(self, data, media_type=None, renderer_context=None):
|
|
||||||
codec = OpenAPICodec()
|
|
||||||
return codec.dump(data)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# API Reference
|
# API Reference
|
||||||
|
|
||||||
## SchemaGenerator
|
## SchemaGenerator
|
||||||
|
@ -407,17 +506,17 @@ This is a good point to override if you want to modify the resulting structure o
|
||||||
as you can build a new dictionary with a different layout.
|
as you can build a new dictionary with a different layout.
|
||||||
|
|
||||||
|
|
||||||
## APIViewSchemaDescriptor
|
## AutoSchema
|
||||||
|
|
||||||
A class that deals with introspection of individual views for schema generation.
|
A class that deals with introspection of individual views for schema generation.
|
||||||
|
|
||||||
`APIViewSchemaDescriptor` is attached to `APIView` via the `schema` attribute.
|
`AutoSchema` is attached to `APIView` via the `schema` attribute.
|
||||||
|
|
||||||
Typically you will subclass `APIViewSchemaDescriptor` to customise schema generation
|
Typically you will subclass `AutoSchema` to customise schema generation
|
||||||
and then set your subclass on your view.
|
and then set your subclass on your view.
|
||||||
|
|
||||||
|
|
||||||
class CustomViewSchema(APIViewSchemaDescriptor):
|
class CustomViewSchema(AutoSchema):
|
||||||
"""
|
"""
|
||||||
Overrides `get_link()` to provide Custom Behavior X
|
Overrides `get_link()` to provide Custom Behavior X
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -256,11 +256,11 @@ class EndpointInspector(object):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class APIViewSchemaDescriptor(object):
|
class ViewInspector(object):
|
||||||
"""
|
"""
|
||||||
Descriptor class on APIView.
|
Descriptor class on APIView.
|
||||||
|
|
||||||
Responsible for per-view instrospection and schema generation.
|
Provide subclass for per-view schema generation
|
||||||
"""
|
"""
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
self.view = instance
|
self.view = instance
|
||||||
|
@ -292,6 +292,16 @@ class APIViewSchemaDescriptor(object):
|
||||||
* method: The HTTP request method.
|
* method: The HTTP request method.
|
||||||
* base_url: The project "mount point" as given to SchemaGenerator
|
* base_url: The project "mount point" as given to SchemaGenerator
|
||||||
"""
|
"""
|
||||||
|
raise NotImplementedError(".get_link() must be overridden.")
|
||||||
|
|
||||||
|
|
||||||
|
class AutoSchema(ViewInspector):
|
||||||
|
"""
|
||||||
|
Default inspector for APIView
|
||||||
|
|
||||||
|
Responsible for per-view instrospection and schema generation.
|
||||||
|
"""
|
||||||
|
def get_link(self, path, method, base_url):
|
||||||
fields = self.get_path_fields(path, method)
|
fields = self.get_path_fields(path, method)
|
||||||
fields += self.get_serializer_fields(path, method)
|
fields += self.get_serializer_fields(path, method)
|
||||||
fields += self.get_pagination_fields(path, method)
|
fields += self.get_pagination_fields(path, method)
|
||||||
|
@ -497,10 +507,10 @@ class APIViewSchemaDescriptor(object):
|
||||||
# - APIView is only used by SchemaView.
|
# - APIView is only used by SchemaView.
|
||||||
# - ???: Make `schemas` a package and move SchemaView to `schema.views`
|
# - ???: Make `schemas` a package and move SchemaView to `schema.views`
|
||||||
# - That way the schema attribute could be set in the class definition.
|
# - That way the schema attribute could be set in the class definition.
|
||||||
APIView.schema = APIViewSchemaDescriptor()
|
APIView.schema = AutoSchema()
|
||||||
|
|
||||||
|
|
||||||
class ManualSchema(APIViewSchemaDescriptor):
|
class ManualSchema(ViewInspector):
|
||||||
"""
|
"""
|
||||||
Overrides get_link to return manually specified schema.
|
Overrides get_link to return manually specified schema.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,7 +12,7 @@ from rest_framework.decorators import detail_route, list_route
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework.schemas import (
|
from rest_framework.schemas import (
|
||||||
APIViewSchemaDescriptor, ManualSchema, SchemaGenerator, get_schema_view
|
AutoSchema, ManualSchema, SchemaGenerator, get_schema_view
|
||||||
)
|
)
|
||||||
from rest_framework.test import APIClient, APIRequestFactory
|
from rest_framework.test import APIClient, APIRequestFactory
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
@ -506,7 +506,7 @@ class TestDescriptor(TestCase):
|
||||||
def test_apiview_schema_descriptor(self):
|
def test_apiview_schema_descriptor(self):
|
||||||
view = APIView()
|
view = APIView()
|
||||||
assert hasattr(view, 'schema')
|
assert hasattr(view, 'schema')
|
||||||
assert isinstance(view.schema, APIViewSchemaDescriptor)
|
assert isinstance(view.schema, AutoSchema)
|
||||||
|
|
||||||
def test_get_link_requires_instance(self):
|
def test_get_link_requires_instance(self):
|
||||||
descriptor = APIView.schema # Accessed from class
|
descriptor = APIView.schema # Accessed from class
|
||||||
|
|
Loading…
Reference in New Issue
Block a user