Refactor to ViewInspector plus AutoSchema

The interface then is **just** `get_link()`
This commit is contained in:
Carlton Gibson 2017-08-30 11:48:02 +02:00
parent 81bac6fe51
commit 416e57f7b8
3 changed files with 156 additions and 47 deletions

View File

@ -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
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
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
is available as `renderers.CoreJSONRenderer`.
### Alternate schema formats
Other schema formats such as [Open API][open-api] ("Swagger"),
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can
also be supported by implementing a custom renderer class.
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can also
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
@ -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
is planned for a future version.
---
# Adding a schema
You'll need to install the `coreapi` package in order to add schema support
for REST framework.
pip install coreapi
# Creating 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
add a schema to your API, depending on exactly what you need.
or allows you to specify one explicitly.
## 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
@ -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
## 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.
## APIViewSchemaDescriptor
## AutoSchema
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.
class CustomViewSchema(APIViewSchemaDescriptor):
class CustomViewSchema(AutoSchema):
"""
Overrides `get_link()` to provide Custom Behavior X
"""

View File

@ -256,11 +256,11 @@ class EndpointInspector(object):
]
class APIViewSchemaDescriptor(object):
class ViewInspector(object):
"""
Descriptor class on APIView.
Responsible for per-view instrospection and schema generation.
Provide subclass for per-view schema generation
"""
def __get__(self, instance, owner):
self.view = instance
@ -292,6 +292,16 @@ class APIViewSchemaDescriptor(object):
* method: The HTTP request method.
* 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_serializer_fields(path, method)
fields += self.get_pagination_fields(path, method)
@ -497,10 +507,10 @@ class APIViewSchemaDescriptor(object):
# - APIView is only used by SchemaView.
# - ???: Make `schemas` a package and move SchemaView to `schema.views`
# - 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.
"""

View File

@ -12,7 +12,7 @@ from rest_framework.decorators import detail_route, list_route
from rest_framework.request import Request
from rest_framework.routers import DefaultRouter
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.views import APIView
@ -506,7 +506,7 @@ class TestDescriptor(TestCase):
def test_apiview_schema_descriptor(self):
view = APIView()
assert hasattr(view, 'schema')
assert isinstance(view.schema, APIViewSchemaDescriptor)
assert isinstance(view.schema, AutoSchema)
def test_get_link_requires_instance(self):
descriptor = APIView.schema # Accessed from class