Skip to content

Commit a803345

Browse files
committed
Refactor to ViewInspector plus AutoSchema
The interface then is **just** `get_link()`
1 parent f248c5e commit a803345

File tree

3 files changed

+155
-46
lines changed

3 files changed

+155
-46
lines changed

docs/api-guide/schemas.md

Lines changed: 139 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ API schemas are a useful tool that allow for a range of use cases, including
1010
generating reference documentation, or driving dynamic client libraries that
1111
can interact with your API.
1212

13-
## Representing schemas internally
13+
## Install Core API
14+
15+
You'll need to install the `coreapi` package in order to add schema support
16+
for REST framework.
17+
18+
pip install coreapi
19+
20+
## Internal schema representation
1421

1522
REST framework uses [Core API][coreapi] in order to model schema information in
1623
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.
6875
REST framework includes a renderer class for handling this media type, which
6976
is available as `renderers.CoreJSONRenderer`.
7077

78+
### Alternate schema formats
79+
7180
Other schema formats such as [Open API][open-api] ("Swagger"),
72-
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can
73-
also be supported by implementing a custom renderer class.
81+
[JSON HyperSchema][json-hyperschema], or [API Blueprint][api-blueprint] can also
82+
be supported by implementing a custom renderer class that handles converting a
83+
`Document` instance into a bytestring representation.
84+
85+
If there is a Core API codec package that supports encoding into the format you
86+
want to use then implementing the renderer class can be done by using the codec.
87+
88+
#### Example
89+
90+
For example, the `openapi_codec` package provides support for encoding or decoding
91+
to the Open API ("Swagger") format:
92+
93+
from rest_framework import renderers
94+
from openapi_codec import OpenAPICodec
95+
96+
class SwaggerRenderer(renderers.BaseRenderer):
97+
media_type = 'application/openapi+json'
98+
format = 'swagger'
99+
100+
def render(self, data, media_type=None, renderer_context=None):
101+
codec = OpenAPICodec()
102+
return codec.dump(data)
103+
104+
105+
74106

75107
## Schemas vs Hypermedia
76108

@@ -89,18 +121,111 @@ document, detailing both the current state and the available interactions.
89121
Further information and support on building Hypermedia APIs with REST framework
90122
is planned for a future version.
91123

124+
92125
---
93126

94-
# Adding a schema
127+
# Creating a schema
95128

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

99-
pip install coreapi
132+
## Manual Schema Specification
100133

101-
REST framework includes functionality for auto-generating a schema,
102-
or allows you to specify one explicitly. There are a few different ways to
103-
add a schema to your API, depending on exactly what you need.
134+
To manually specify a schema you create a Core API `Document`, similar to the
135+
example above.
136+
137+
schema = coreapi.Document(
138+
title='Flight Search API',
139+
content={
140+
...
141+
}
142+
)
143+
144+
145+
## Automatic Schema Generation
146+
147+
Automatic schema generation is provided by the `SchemaGenerator` class.
148+
149+
`SchemaGenerator` processes a list of routed URL pattterns and compiles the
150+
appropriately structured Core API Document.
151+
152+
Basic usage is just to provide the title for your schema and call
153+
`get_schema()`:
154+
155+
generator = schemas.SchemaGenerator(title='Flight Search API')
156+
schema = generator.get_schema()
157+
158+
### Per-View Schema Customisation
159+
160+
By default, view introspection is performed by an `AutoSchema` instance
161+
accessible via the `schema` attribute on `APIView`. This provides the
162+
appropriate Core API `Link` object for the view, request method and path:
163+
164+
auto_schema = view.schema
165+
coreapi_link = auto_schema.get_link(...)
166+
167+
168+
(Aside: In compiling the schema, `SchemaGenerator` calls `view.schema.get_link()` for
169+
each view, allowed method and path.)
170+
171+
To customise the `Link` generation you may:
172+
173+
* Instantiate `AutoSchema` on your view with the `manual_fields` kwarg:
174+
175+
from rest_framework.views import APIView
176+
from rest_framework.schemas import AutoSchema
177+
178+
class CustomView(APIView):
179+
...
180+
schema = AutoSchema(
181+
manual_fields= {
182+
"extra_field": coreapi.Field(...)
183+
}
184+
)
185+
186+
This allows extension for the most common case without subclassing.
187+
188+
* Provide an `AutoSchema` subclass with more complex customisation:
189+
190+
from rest_framework.views import APIView
191+
from rest_framework.schemas import AutoSchema
192+
193+
class CustomSchema(AutoSchema):
194+
def get_link(...):
195+
# Implemet custom introspection here (or in other sub-methods)
196+
197+
class CustomView(APIView):
198+
...
199+
schema = CustomSchema()
200+
201+
This provides complete control over view introspection.
202+
203+
* Instantiate `ManualSchema` on your view, providing the Core API `Link`
204+
explicitly:
205+
206+
from rest_framework.views import APIView
207+
from rest_framework.schemas import ManualSchema
208+
209+
class CustomView(APIView):
210+
...
211+
schema = ManualSchema(
212+
coreapi.Link(...)
213+
)
214+
215+
This allows manually specifying the schema for some views whilst maintaining
216+
automatic generation elsewhere.
217+
218+
---
219+
220+
**Note**: For full details on `SchemaGenerator` plus the `AutoSchema` and
221+
`ManualSchema` descriptors see the [API Reference below](#api-reference).
222+
223+
---
224+
225+
# Adding a schema view
226+
227+
There are a few different ways to add a schema view to your API, depending on
228+
exactly what you need.
104229

105230
## The get_schema_view shortcut
106231

@@ -342,32 +467,6 @@ A generic viewset with sections in the class docstring, using multi-line style.
342467

343468
---
344469

345-
# Alternate schema formats
346-
347-
In order to support an alternate schema format, you need to implement a custom renderer
348-
class that handles converting a `Document` instance into a bytestring representation.
349-
350-
If there is a Core API codec package that supports encoding into the format you
351-
want to use then implementing the renderer class can be done by using the codec.
352-
353-
## Example
354-
355-
For example, the `openapi_codec` package provides support for encoding or decoding
356-
to the Open API ("Swagger") format:
357-
358-
from rest_framework import renderers
359-
from openapi_codec import OpenAPICodec
360-
361-
class SwaggerRenderer(renderers.BaseRenderer):
362-
media_type = 'application/openapi+json'
363-
format = 'swagger'
364-
365-
def render(self, data, media_type=None, renderer_context=None):
366-
codec = OpenAPICodec()
367-
return codec.dump(data)
368-
369-
---
370-
371470
# API Reference
372471

373472
## SchemaGenerator
@@ -407,17 +506,17 @@ This is a good point to override if you want to modify the resulting structure o
407506
as you can build a new dictionary with a different layout.
408507

409508

410-
## APIViewSchemaDescriptor
509+
## AutoSchema
411510

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

414-
`APIViewSchemaDescriptor` is attached to `APIView` via the `schema` attribute.
513+
`AutoSchema` is attached to `APIView` via the `schema` attribute.
415514

416-
Typically you will subclass `APIViewSchemaDescriptor` to customise schema generation
515+
Typically you will subclass `AutoSchema` to customise schema generation
417516
and then set your subclass on your view.
418517

419518

420-
class CustomViewSchema(APIViewSchemaDescriptor):
519+
class CustomViewSchema(AutoSchema):
421520
"""
422521
Overrides `get_link()` to provide Custom Behavior X
423522
"""

rest_framework/schemas.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,11 @@ def get_allowed_methods(self, callback):
256256
]
257257

258258

259-
class APIViewSchemaDescriptor(object):
259+
class ViewInspector(object):
260260
"""
261261
Descriptor class on APIView.
262262
263-
Responsible for per-view instrospection and schema generation.
263+
Provide subclass for per-view schema generation
264264
"""
265265
def __get__(self, instance, owner):
266266
self.view = instance
@@ -292,6 +292,16 @@ def get_link(self, path, method, base_url):
292292
* method: The HTTP request method.
293293
* base_url: The project "mount point" as given to SchemaGenerator
294294
"""
295+
raise NotImplementedError(".get_link() must be overridden.")
296+
297+
298+
class AutoSchema(ViewInspector):
299+
"""
300+
Default inspector for APIView
301+
302+
Responsible for per-view instrospection and schema generation.
303+
"""
304+
def get_link(self, path, method, base_url):
295305
fields = self.get_path_fields(path, method)
296306
fields += self.get_serializer_fields(path, method)
297307
fields += self.get_pagination_fields(path, method)
@@ -497,10 +507,10 @@ def get_encoding(self, path, method):
497507
# - APIView is only used by SchemaView.
498508
# - ???: Make `schemas` a package and move SchemaView to `schema.views`
499509
# - That way the schema attribute could be set in the class definition.
500-
APIView.schema = APIViewSchemaDescriptor()
510+
APIView.schema = AutoSchema()
501511

502512

503-
class ManualSchema(APIViewSchemaDescriptor):
513+
class ManualSchema(ViewInspector):
504514
"""
505515
Overrides get_link to return manually specified schema.
506516
"""

tests/test_schemas.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from rest_framework.request import Request
1313
from rest_framework.routers import DefaultRouter
1414
from rest_framework.schemas import (
15-
APIViewSchemaDescriptor, ManualSchema, SchemaGenerator, get_schema_view
15+
AutoSchema, ManualSchema, SchemaGenerator, get_schema_view
1616
)
1717
from rest_framework.test import APIClient, APIRequestFactory
1818
from rest_framework.views import APIView
@@ -506,7 +506,7 @@ class TestDescriptor(TestCase):
506506
def test_apiview_schema_descriptor(self):
507507
view = APIView()
508508
assert hasattr(view, 'schema')
509-
assert isinstance(view.schema, APIViewSchemaDescriptor)
509+
assert isinstance(view.schema, AutoSchema)
510510

511511
def test_get_link_requires_instance(self):
512512
descriptor = APIView.schema # Accessed from class

0 commit comments

Comments
 (0)