|
| 1 | +source: schemas.py |
| 2 | + |
| 3 | +#Schemas |
| 4 | + |
| 5 | +>A machine-readable[schema] describes what resources are available via the API, what their URLs are, how they are represented and what operations they support. |
| 6 | +> |
| 7 | +>— Heroku,[JSON Schema for the Heroku Platform API][cite] |
| 8 | +
|
| 9 | +API schemas are a useful tool that allow for a range of use cases, including |
| 10 | +generating reference documentation, or driving dynamic client libraries that |
| 11 | +can interact with your API. |
| 12 | + |
| 13 | +##Representing schemas internally |
| 14 | + |
| 15 | +REST framework uses[Core API][coreapi] in order to model schema information in |
| 16 | +a format-independent representation. This information can then be rendered |
| 17 | +into various different schema formats, or used to generate API documentation. |
| 18 | + |
| 19 | +When using Core API, a schema is represented as a`Document` which is the |
| 20 | +top-level container object for information about the API. Available API |
| 21 | +interactions are represented using`Link` objects. Each link includes a URL, |
| 22 | +HTTP method, and may include a list of`Field` instances, which describe any |
| 23 | +parameters that may be accepted by the API endpoint. The`Link` and`Field` |
| 24 | +instances may also include descriptions, that allow an API schema to be |
| 25 | +rendered into user documentation. |
| 26 | + |
| 27 | +Here's an example of an API description that includes a single`search` |
| 28 | +endpoint: |
| 29 | + |
| 30 | +coreapi.Document( |
| 31 | + title='Flight Search API', |
| 32 | + url='https://api.example.org/', |
| 33 | + content={ |
| 34 | + 'search': coreapi.Link( |
| 35 | + url='/search/', |
| 36 | + action='get', |
| 37 | + fields=[ |
| 38 | + coreapi.Field( |
| 39 | + name='from', |
| 40 | + required=True, |
| 41 | + location='query', |
| 42 | + description='City name or airport code.' |
| 43 | + ), |
| 44 | + coreapi.Field( |
| 45 | + name='to', |
| 46 | + required=True, |
| 47 | + location='query', |
| 48 | + description='City name or airport code.' |
| 49 | + ), |
| 50 | + coreapi.Field( |
| 51 | + name='date', |
| 52 | + required=True, |
| 53 | + location='query', |
| 54 | + description='Flight date in "YYYY-MM-DD" format.' |
| 55 | + ) |
| 56 | + ], |
| 57 | + description='Return flight availability and prices.' |
| 58 | + ) |
| 59 | + } |
| 60 | +) |
| 61 | + |
| 62 | +##Schema output formats |
| 63 | + |
| 64 | +In order to be presented in an HTTP response, the internal representation |
| 65 | +has to be rendered into the actual bytes that are used in the response. |
| 66 | + |
| 67 | +[Core JSON][corejson] is designed as a canonical format for use with Core API. |
| 68 | +REST framework includes a renderer class for handling this media type, which |
| 69 | +is available as`renderers.CoreJSONRenderer`. |
| 70 | + |
| 71 | +Other schema formats such as[Open API][open-api] (Formerly "Swagger"), |
| 72 | +[JSON HyperSchema][json-hyperschema], or[API Blueprint][api-blueprint] can |
| 73 | +also be supported by implementing a custom renderer class. |
| 74 | + |
| 75 | +##Schemas vs Hypermedia |
| 76 | + |
| 77 | +It's worth pointing out here that Core API can also be used to model hypermedia |
| 78 | +responses, which present an alternative interaction style to API schemas. |
| 79 | + |
| 80 | +With an API schema, the entire available interface is presented up-front |
| 81 | +as a single endpoint. Responses to individual API endpoints are then typically |
| 82 | +presented as plain data, without any further interactions contained in each |
| 83 | +response. |
| 84 | + |
| 85 | +With Hypermedia, the client is instead presented with a document containing |
| 86 | +both data and available interactions. Each interaction results in a new |
| 87 | +document, detailing both the current state and the available interactions. |
| 88 | + |
| 89 | +Further information and support on building Hypermedia APIs with REST framework |
| 90 | +is planned for a future version. |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +#Adding a schema |
| 95 | + |
| 96 | +You'll need to install the`coreapi` package in order to add schema support |
| 97 | +for REST framework. |
| 98 | + |
| 99 | +pip install coreapi |
| 100 | + |
| 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. |
| 104 | + |
| 105 | +##Using DefaultRouter |
| 106 | + |
| 107 | +If you're using`DefaultRouter` then you can include an auto-generated schema, |
| 108 | +simply by adding a`schema_title` argument to the router. |
| 109 | + |
| 110 | +router = DefaultRouter(schema_title='Server Monitoring API') |
| 111 | + |
| 112 | +The schema will be included at the root URL,`/`, and presented to clients |
| 113 | +that include the Core JSON media type in their`Accept` header. |
| 114 | + |
| 115 | +$ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json |
| 116 | +HTTP/1.0 200 OK |
| 117 | +Allow: GET, HEAD, OPTIONS |
| 118 | +Content-Type: application/vnd.coreapi+json |
| 119 | + |
| 120 | +{ |
| 121 | + "_meta": { |
| 122 | + "title": "Server Monitoring API" |
| 123 | + }, |
| 124 | + "_type": "document", |
| 125 | + ... |
| 126 | +} |
| 127 | + |
| 128 | +This is a great zero-configuration option for when you want to get up and |
| 129 | +running really quickly. If you want a little more flexibility over the |
| 130 | +schema output then you'll need to consider using`SchemaGenerator` instead. |
| 131 | + |
| 132 | +##Using SchemaGenerator |
| 133 | + |
| 134 | +The most common way to add a schema to your API is to use the`SchemaGenerator` |
| 135 | +class to auto-generate the`Document` instance, and to return that from a view. |
| 136 | + |
| 137 | +This option gives you the flexibility of setting up the schema endpoint |
| 138 | +with whatever behaviour you want. For example, you can apply different |
| 139 | +permission, throttling or authentication policies to the schema endpoint. |
| 140 | + |
| 141 | +Here's an example of using`SchemaGenerator` together with a view to |
| 142 | +return the schema. |
| 143 | + |
| 144 | +**views.py:** |
| 145 | + |
| 146 | +from rest_framework.decorators import api_view, renderer_classes |
| 147 | +from rest_framework import renderers, schemas |
| 148 | + |
| 149 | +generator = schemas.SchemaGenerator(title='Bookings API') |
| 150 | + |
| 151 | +@api_view() |
| 152 | +@renderer_classes([renderers.CoreJSONRenderer]) |
| 153 | +def schema_view(request): |
| 154 | + return generator.get_schema() |
| 155 | + |
| 156 | +**urls.py:** |
| 157 | + |
| 158 | +urlpatterns = [ |
| 159 | + url('/', schema_view), |
| 160 | + ... |
| 161 | +] |
| 162 | + |
| 163 | +You can also serve different schemas to different users, depending on the |
| 164 | +permissions they have available. This approach can be used to ensure that |
| 165 | +unauthenticated requests are presented with a different schema to |
| 166 | +authenticated requests, or to ensure that different parts of the API are |
| 167 | +made visible to different users depending on their role. |
| 168 | + |
| 169 | +In order to present a schema with endpoints filtered by user permissions, |
| 170 | +you need to pass the`request` argument to the`get_schema()` method, like so: |
| 171 | + |
| 172 | +@api_view() |
| 173 | +@renderer_classes([renderers.CoreJSONRenderer]) |
| 174 | +def schema_view(request): |
| 175 | + return generator.get_schema(request=request) |
| 176 | + |
| 177 | +##Explicit schema definition |
| 178 | + |
| 179 | +An alternative to the auto-generated approach is to specify the API schema |
| 180 | +explicitly, by declaring a`Document` object in your codebase. Doing so is a |
| 181 | +little more work, but ensures that you have full control over the schema |
| 182 | +representation. |
| 183 | + |
| 184 | +import coreapi |
| 185 | +from rest_framework.decorators import api_view, renderer_classes |
| 186 | +from rest_framework import renderers |
| 187 | + |
| 188 | +schema = coreapi.Document( |
| 189 | + title='Bookings API', |
| 190 | + content={ |
| 191 | + ... |
| 192 | + } |
| 193 | +) |
| 194 | + |
| 195 | +@api_view() |
| 196 | +@renderer_classes([renderers.CoreJSONRenderer]) |
| 197 | +def schema_view(request): |
| 198 | + return schema |
| 199 | + |
| 200 | +##Static schema file |
| 201 | + |
| 202 | +A final option is to write your API schema as a static file, using one |
| 203 | +of the available formats, such as Core JSON or Open API. |
| 204 | + |
| 205 | +You could then either: |
| 206 | + |
| 207 | +* Write a schema definition as a static file, and[serve the static file directly][static-files]. |
| 208 | +* Write a schema definition that is loaded using`Core API`, and then |
| 209 | + rendered to one of many available formats, depending on the client request. |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +#API Reference |
| 214 | + |
| 215 | +##SchemaGenerator |
| 216 | + |
| 217 | +A class that deals with introspecting your API views, which can be used to |
| 218 | +generate a schema. |
| 219 | + |
| 220 | +Typically you'll instantiate`SchemaGenerator` with a single argument, like so: |
| 221 | + |
| 222 | +generator = SchemaGenerator(title='Stock Prices API') |
| 223 | + |
| 224 | +Arguments: |
| 225 | + |
| 226 | +*`title` - The name of the API.**required** |
| 227 | +*`patterns` - A list of URLs to inspect when generating the schema. Defaults to the project's URL conf. |
| 228 | +*`urlconf` - A URL conf module name to use when generating the schema. Defaults to`settings.ROOT_URLCONF`. |
| 229 | + |
| 230 | +###get_schema() |
| 231 | + |
| 232 | +Returns a`coreapi.Document` instance that represents the API schema. |
| 233 | + |
| 234 | +@api_view |
| 235 | +@renderer_classes([renderers.CoreJSONRenderer]) |
| 236 | +def schema_view(request): |
| 237 | + return generator.get_schema() |
| 238 | + |
| 239 | +Arguments: |
| 240 | + |
| 241 | +*`request` - The incoming request. Optionally used if you want to apply per-user permissions to the schema-generation. |
| 242 | + |
| 243 | +--- |
| 244 | + |
| 245 | +##Core API |
| 246 | + |
| 247 | +This documentation gives a brief overview of the components within the`coreapi` |
| 248 | +package that are used to represent an API schema. |
| 249 | + |
| 250 | +Note that these classes are imported from the`coreapi` package, rather than |
| 251 | +from the`rest_framework` package. |
| 252 | + |
| 253 | +###Document |
| 254 | + |
| 255 | +Represents a container for the API schema. |
| 256 | + |
| 257 | +####`title` |
| 258 | + |
| 259 | +A name for the API. |
| 260 | + |
| 261 | +####`url` |
| 262 | + |
| 263 | +A canonical URL for the API. |
| 264 | + |
| 265 | +####`content` |
| 266 | + |
| 267 | +A dictionary, containing the`Link` objects that the schema contains. |
| 268 | + |
| 269 | +In order to provide more structure to the schema, the`content` dictionary |
| 270 | +may be nested, typically to a second level. For example: |
| 271 | + |
| 272 | +content={ |
| 273 | + "bookings": { |
| 274 | + "list": Link(...), |
| 275 | + "create": Link(...), |
| 276 | + ... |
| 277 | + }, |
| 278 | + "venues": { |
| 279 | + "list": Link(...), |
| 280 | + ... |
| 281 | + }, |
| 282 | + ... |
| 283 | +} |
| 284 | + |
| 285 | +###Link |
| 286 | + |
| 287 | +Represents an individual API endpoint. |
| 288 | + |
| 289 | +####`url` |
| 290 | + |
| 291 | +The URL of the endpoint. May be a URI template, such as`/users/{username}/`. |
| 292 | + |
| 293 | +####`action` |
| 294 | + |
| 295 | +The HTTP method associated with the endpoint. Note that URLs that support |
| 296 | +more than one HTTP method, should correspond to a single`Link` for each. |
| 297 | + |
| 298 | +####`fields` |
| 299 | + |
| 300 | +A list of`Field` instances, describing the available parameters on the input. |
| 301 | + |
| 302 | +####`description` |
| 303 | + |
| 304 | +A short description of the meaning and intended usage of the endpoint. |
| 305 | + |
| 306 | +###Field |
| 307 | + |
| 308 | +Represents a single input parameter on a given API endpoint. |
| 309 | + |
| 310 | +####`name` |
| 311 | + |
| 312 | +A descriptive name for the input. |
| 313 | + |
| 314 | +####`required` |
| 315 | + |
| 316 | +A boolean, indicated if the client is required to included a value, or if |
| 317 | +the parameter can be omitted. |
| 318 | + |
| 319 | +####`location` |
| 320 | + |
| 321 | +Determines how the information is encoded into the request. Should be one of |
| 322 | +the following strings: |
| 323 | + |
| 324 | +**"path"** |
| 325 | + |
| 326 | +Included in a templated URI. For example a`url` value of`/products/{product_code}/` could be used together with a`"path"` field, to handle API inputs in a URL path such as`/products/slim-fit-jeans/`. |
| 327 | + |
| 328 | +These fields will normally correspond with[named arguments in the project URL conf][named-arguments]. |
| 329 | + |
| 330 | +**"query"** |
| 331 | + |
| 332 | +Included as a URL query parameter. For example`?search=sale`. Typically for`GET` requests. |
| 333 | + |
| 334 | +These fields will normally correspond with pagination and filtering controls on a view. |
| 335 | + |
| 336 | +**"form"** |
| 337 | + |
| 338 | +Included in the request body, as a single item of a JSON object or HTML form. For example`{"colour": "blue", ...}`. Typically for`POST`,`PUT` and`PATCH` requests. Multiple`"form"` fields may be included on a single link. |
| 339 | + |
| 340 | +These fields will normally correspond with serializer fields on a view. |
| 341 | + |
| 342 | +**"body"** |
| 343 | + |
| 344 | +Included as the complete request body. Typically for`POST`,`PUT` and`PATCH` requests. No more than one`"body"` field may exist on a link. May not be used together with`"form"` fields. |
| 345 | + |
| 346 | +These fields will normally correspond with views that use`ListSerializer` to validate the request input, or with file upload views. |
| 347 | + |
| 348 | +####`encoding` |
| 349 | + |
| 350 | +**"application/json"** |
| 351 | + |
| 352 | +JSON encoded request content. Corresponds to views using`JSONParser`. |
| 353 | +Valid only if either one or more`location="form"` fields, or a single |
| 354 | +`location="body"` field is included on the`Link`. |
| 355 | + |
| 356 | +**"multipart/form-data"** |
| 357 | + |
| 358 | +Multipart encoded request content. Corresponds to views using`MultiPartParser`. |
| 359 | +Valid only if one or more`location="form"` fields is included on the`Link`. |
| 360 | + |
| 361 | +**"application/x-www-form-urlencoded"** |
| 362 | + |
| 363 | +URL encoded request content. Corresponds to views using`FormParser`. Valid |
| 364 | +only if one or more`location="form"` fields is included on the`Link`. |
| 365 | + |
| 366 | +**"application/octet-stream"** |
| 367 | + |
| 368 | +Binary upload request content. Corresponds to views using`FileUploadParser`. |
| 369 | +Valid only if a`location="body"` field is included on the`Link`. |
| 370 | + |
| 371 | +####`description` |
| 372 | + |
| 373 | +A short description of the meaning and intended usage of the input field. |
| 374 | + |
| 375 | + |
| 376 | +[cite]:https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api |
| 377 | +[coreapi]:http://www.coreapi.org/ |
| 378 | +[corejson]:http://www.coreapi.org/specification/encoding/#core-json-encoding |
| 379 | +[open-api]:https://openapis.org/ |
| 380 | +[json-hyperschema]:http://json-schema.org/latest/json-schema-hypermedia.html |
| 381 | +[api-blueprint]:https://apiblueprint.org/ |
| 382 | +[static-files]:https://docs.djangoproject.com/en/dev/howto/static-files/ |
| 383 | +[named-arguments]:https://docs.djangoproject.com/en/dev/topics/http/urls/#named-groups |