Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor schema generation to allow per-view customisation #5354

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8f13e9c
Initial Refactor Step
carltongibson Aug 23, 2017
611af4f
Move `get_link` to descriptor
carltongibson Aug 23, 2017
61ce8bf
Move `get_description` to descriptor
carltongibson Aug 23, 2017
f180397
Remove need for generator in get_description
carltongibson Aug 23, 2017
238c599
Move get_path_fields to descriptor
carltongibson Aug 23, 2017
0a173ef
Move `get_serializer_fields` to descriptor
carltongibson Aug 23, 2017
1d6562e
Move `get_pagination_fields` to descriptor
carltongibson Aug 23, 2017
aad9289
Move `get_filter_fields` to descriptor
carltongibson Aug 23, 2017
63f6377
Move `get_encoding` to descriptor.
carltongibson Aug 23, 2017
183f13c
Pass just `url` from SchemaGenerator to descriptor
carltongibson Aug 23, 2017
dc92b55
Make `view` a property
carltongibson Aug 23, 2017
7aa52ac
Adjust API Reference docs
carltongibson Aug 29, 2017
81bac6f
Add `ManualSchema` class
carltongibson Aug 29, 2017
416e57f
Refactor to `ViewInspector` plus `AutoSchema`
carltongibson Aug 30, 2017
398824f
Add `manual_fields` kwarg to AutoSchema
carltongibson Aug 30, 2017
ef9e5f2
Add schema decorator for FBVs
carltongibson Sep 4, 2017
96c39e5
Adjust comments
carltongibson Sep 5, 2017
18defaf
Docs: Provide full params in example
carltongibson Sep 6, 2017
344e274
Add docstring for ViewInstpector.__get__ descriptor method.
carltongibson Sep 6, 2017
9fa8a05
Make `schemas` a package.
carltongibson Sep 6, 2017
18575c9
Split generators, inspectors, views.
carltongibson Sep 6, 2017
e7f3219
Adjust imports
carltongibson Sep 6, 2017
35e058c
Rename to EndpointEnumerator
carltongibson Sep 6, 2017
a7b2f50
Adjust ManualSchema to take `fields`
carltongibson Sep 14, 2017
a958a3e
Add package/module docstrings
carltongibson Sep 14, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 223 additions & 45 deletions docs/api-guide/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -89,18 +121,121 @@ 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
# Creating a schema

You'll need to install the `coreapi` package in order to add schema support
for REST framework.
REST framework includes functionality for auto-generating a schema,
or allows you to specify one explicitly.

pip install coreapi
## Manual Schema Specification

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.
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(...)

(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=[
coreapi.Field("extra_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 `Fields` for
the view explicitly:

from rest_framework.views import APIView
from rest_framework.schemas import ManualSchema

class CustomView(APIView):
...
schema = ManualSchema(fields=[
coreapi.Field(
"first_field",
required=True,
location="path",
schema=coreschema.String()
),
coreapi.Field(
"second_field",
required=True,
location="path",
schema=coreschema.String()
),
])

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

Expand Down Expand Up @@ -342,38 +477,12 @@ 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

A class that deals with introspecting your API views, which can be used to
generate a schema.
A class that walks a list of routed URL patterns, requests the schema for each view,
and collates the resulting CoreAPI Document.

Typically you'll instantiate `SchemaGenerator` with a single argument, like so:

Expand Down Expand Up @@ -406,39 +515,108 @@ Return a nested dictionary containing all the links that should be included in t
This is a good point to override if you want to modify the resulting structure of the generated schema,
as you can build a new dictionary with a different layout.

### get_link(self, path, method, view)

## AutoSchema

A class that deals with introspection of individual views for schema generation.

`AutoSchema` is attached to `APIView` via the `schema` attribute.

The `AutoSchema` constructor takes a single keyword argument `manual_fields`.

**`manual_fields`**: a `list` of `coreapi.Field` instances that will be added to
the generated fields. Generated fields with a matching `name` will be overwritten.

class CustomView(APIView):
schema = AutoSchema(manual_fields=[
coreapi.Field(
"my_extra_field",
required=True,
location="path",
schema=coreschema.String()
),
])

For more advanced customisation subclass `AutoSchema` to customise schema generation.

class CustomViewSchema(AutoSchema):
"""
Overrides `get_link()` to provide Custom Behavior X
"""

def get_link(self, path, method, base_url):
link = super().get_link(path, method, base_url)
# Do something to customize link here...
return link

class MyView(APIView):
schema = CustomViewSchema()

The following methods are available to override.

### get_link(self, path, method, base_url)

Returns a `coreapi.Link` instance corresponding to the given view.

This is the main entry point.
You can override this if you need to provide custom behaviors for particular views.

### get_description(self, path, method, view)
### get_description(self, path, method)

Returns a string to use as the link description. By default this is based on the
view docstring as described in the "Schemas as Documentation" section above.

### get_encoding(self, path, method, view)
### get_encoding(self, path, method)

Returns a string to indicate the encoding for any request body, when interacting
with the given view. Eg. `'application/json'`. May return a blank string for views
that do not expect a request body.

### get_path_fields(self, path, method, view):
### get_path_fields(self, path, method):

Return a list of `coreapi.Link()` instances. One for each path parameter in the URL.

### get_serializer_fields(self, path, method, view)
### get_serializer_fields(self, path, method)

Return a list of `coreapi.Link()` instances. One for each field in the serializer class used by the view.

### get_pagination_fields(self, path, method, view
### get_pagination_fields(self, path, method)

Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method on any pagination class used by the view.

### get_filter_fields(self, path, method, view)
### get_filter_fields(self, path, method)

Return a list of `coreapi.Link()` instances, as returned by the `get_schema_fields()` method of any filter classes used by the view.


## ManualSchema

Allows manually providing a list of `coreapi.Field` instances for the schema,
plus an optional description.

class MyView(APIView):
schema = ManualSchema(fields=[
coreapi.Field(
"first_field",
required=True,
location="path",
schema=coreschema.String()
),
coreapi.Field(
"second_field",
required=True,
location="path",
schema=coreschema.String()
),
]
)

The `ManualSchema` constructor takes two arguments:

**`fields`**: A list of `coreapi.Field` instances. Required.

**`description`**: A string description. Optional.

---

## Core API
Expand Down
22 changes: 22 additions & 0 deletions docs/api-guide/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,28 @@ The available decorators are:

Each of these decorators takes a single argument which must be a list or tuple of classes.


## View schema decorator

To override the default schema generation for function based views you may use
the `@schema` decorator. This must come *after* (below) the `@api_view`
decorator. For example:

from rest_framework.decorators import api_view, schema
from rest_framework.schemas import AutoSchema

class CustomAutoSchema(AutoSchema):
def get_link(self, path, method, base_url):
# override view introspection here...

@api_view(['GET'])
@schema(CustomAutoSchema())
def view(request):
return Response({"message": "Hello for today! See you tomorrow!"})

This decorator takes a single `AutoSchema` instance, an `AutoSchema` subclass
instance or `ManualSchema` instance as described in the [Schemas documentation][schemas],

[cite]: http://reinout.vanrees.org/weblog/2011/08/24/class-based-views-usage.html
[cite2]: http://www.boredomandlaziness.org/2012/05/djangos-cbvs-are-not-mistake-but.html
[settings]: settings.md
Expand Down
10 changes: 10 additions & 0 deletions rest_framework/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def handler(self, *args, **kwargs):
WrappedAPIView.permission_classes = getattr(func, 'permission_classes',
APIView.permission_classes)

WrappedAPIView.schema = getattr(func, 'schema',
APIView.schema)

WrappedAPIView.exclude_from_schema = exclude_from_schema
return WrappedAPIView.as_view()
return decorator
Expand Down Expand Up @@ -112,6 +115,13 @@ def decorator(func):
return decorator


def schema(view_inspector):
def decorator(func):
func.schema = view_inspector
return func
return decorator


def detail_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for detail requests.
Expand Down
Loading