-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Schemas & client libraries. #4179
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
Changes from 18 commits
bc836aa
b64340b
c890ad4
2d28390
744dba4
56ece73
99adbf1
80c595e
47c7765
29e228d
eeffca4
6c60f58
b7fcdd2
2e60f41
2ffa145
b709dd4
474a23e
4822896
1f76cca
cad24b1
8fb2602
b438281
8519b4e
2f5c974
e78753d
84bb5ea
63e8467
bdbcb33
7236af3
89540ab
e3ced75
12be5b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| # Tutorial 7: Schemas & client libraries | ||
|
|
||
| A schema is a machine-readable document that describes the available API | ||
| endpoints, their URLS, and what operations they support. | ||
|
|
||
| Schemas can be a useful tool for auto-generated documentation, and can also | ||
| be used to drive dynamic client libraries that can interact with the API. | ||
|
|
||
| ## Core API | ||
|
|
||
| In order to provide schema support REST framework uses [Core API][coreapi]. | ||
|
|
||
| Core API is a document specification for describing APIs. It is used to provide | ||
| an internal representation format of the available endpoints and possible | ||
| interactions that an API exposes. It can either be used server-side, or | ||
| client-side. | ||
|
|
||
| When used server-side, Core API allows an API to support rendering to a wide | ||
| range of schema or hypermedia formats. | ||
|
|
||
| When used client-side, Core API allows for dynamically driven client libraries | ||
| that can interact with any API that exposes a supported schema or hypermedia | ||
| format. | ||
|
|
||
| ## Adding a schema | ||
|
|
||
| REST framework supports either explicitly defined schema views, or | ||
| automatically generated schemas. Since we're using viewsets and routers, | ||
| we can simply use the automatic schema generation. | ||
|
|
||
| You'll need to install the `coreapi` python package in order to include an | ||
| API schema. | ||
|
|
||
| $ pip install coreapi | ||
|
|
||
| We can now include a schema for our API, by adding a `schema_title` argument to | ||
| the router instantiation. | ||
|
|
||
| router = DefaultRouter(schema_title='Pastebin API') | ||
|
|
||
| If you visit the API root endpoint in a browser you should now see `corejson` | ||
| representation become available as an option. | ||
|
|
||
|  | ||
|
|
||
| We can also request the schema from the command line, by specifying the desired | ||
| content type in the `Accept` header. | ||
|
|
||
| $ http http://127.0.0.1:8000/ Accept:application/vnd.coreapi+json | ||
| HTTP/1.0 200 OK | ||
| Allow: GET, HEAD, OPTIONS | ||
| Content-Type: application/vnd.coreapi+json | ||
|
|
||
| { | ||
| "_meta": { | ||
| "title": "Pastebin API" | ||
| }, | ||
| "_type": "document", | ||
| ... | ||
|
|
||
| The default output style is to use the [Core JSON][corejson] encoding. | ||
|
|
||
| Other schema formats, such as [Open API][openapi] (formerly Swagger) are | ||
| also supported. | ||
|
|
||
| ## Using a command line client | ||
|
|
||
| Now that our API is exposing a schema endpoint, we can use a dynamic client | ||
| library to interact with the API. To demonstrate this, let's use the | ||
| Core API command line client. We've already installed the `coreapi` package | ||
| using `pip`, so the client tool should already be installed. Check that it | ||
| is available on the command line... | ||
|
|
||
| $ coreapi | ||
| Usage: coreapi [OPTIONS] COMMAND [ARGS]... | ||
|
|
||
| Command line client for interacting with CoreAPI services. | ||
|
|
||
| Visit http://www.coreapi.org for more information. | ||
|
|
||
| Options: | ||
| --version Display the package version number. | ||
| --help Show this message and exit. | ||
|
|
||
| Commands: | ||
| ... | ||
|
|
||
| First we'll load the API schema using the command line client. | ||
|
|
||
| $ coreapi get http://127.0.0.1:8000/ | ||
| <Pastebin API "http://127.0.0.1:8000/"> | ||
| snippets: { | ||
| highlight(pk) | ||
| list() | ||
| retrieve(pk) | ||
| } | ||
| users: { | ||
| list() | ||
| retrieve(pk) | ||
| } | ||
|
|
||
| We haven't authenticated yet, so right now we're only able to see the read only | ||
| endpoints, in line with how we've set up the permissions on the API. | ||
|
|
||
| Let's try listing the existing snippets, using the command line client: | ||
|
|
||
| $ coreapi action snippets list | ||
| [ | ||
| { | ||
| "url": "http://127.0.0.1:8000/snippets/1/", | ||
| "highlight": "http://127.0.0.1:8000/snippets/1/highlight/", | ||
| "owner": "lucy", | ||
| "title": "Example", | ||
| "code": "print('hello, world!')", | ||
| "linenos": true, | ||
| "language": "python", | ||
| "style": "friendly" | ||
| }, | ||
| ... | ||
|
|
||
| Some of the API endpoints require named parameters. For example, to get back | ||
| the highlight HTML for a particular snippet we need to provide an id. | ||
|
|
||
| $ coreapi action snippets highlight --param pk 1 | ||
| <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> | ||
|
|
||
| <html> | ||
| <head> | ||
| <title>Example</title> | ||
| ... | ||
|
|
||
| ## Authenticating our client | ||
|
|
||
| If we want to be able to create, edit and delete snippets, we'll need to | ||
| authenticate as a valid user. In this case we'll just use basic auth. | ||
|
|
||
| Make sure to replace the `<username>` and `<password>` below with your | ||
| actual username and password. | ||
|
|
||
| $ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic | ||
| Added credentials | ||
| 127.0.0.1 "Basic <...>" | ||
|
|
||
| Now if we fetch the schema again, we should be able to see the full | ||
| set of available interactions. | ||
|
|
||
| $ coreapi reload | ||
| Pastebin API "http://127.0.0.1:8000/"> | ||
| snippets: { | ||
| create(code, [title], [linenos], [language], [style]) | ||
| destroy(pk) | ||
| highlight(pk) | ||
| list() | ||
| partial_update(pk, [title], [code], [linenos], [language], [style]) | ||
| retrieve(pk) | ||
| update(pk, code, [title], [linenos], [language], [style]) | ||
| } | ||
| users: { | ||
| list() | ||
| retrieve(pk) | ||
| } | ||
|
|
||
| We're now able to interact with these endpoints. For example, to create a new | ||
| snippet: | ||
|
|
||
| $ coreapi action snippets create --param title "Example" --param code "print('hello, world')" | ||
| { | ||
| "url": "http://127.0.0.1:8000/snippets/7/", | ||
| "id": 7, | ||
| "highlight": "http://127.0.0.1:8000/snippets/7/highlight/", | ||
| "owner": "lucy", | ||
| "title": "Example", | ||
| "code": "print('hello, world')", | ||
| "linenos": false, | ||
| "language": "python", | ||
| "style": "friendly" | ||
| } | ||
|
|
||
| And to delete a snippet: | ||
|
|
||
| $ coreapi action snippets destroy --param pk 7 | ||
|
|
||
| As well as the command line client, developers can also interact with your | ||
| API using client libraries. The Python client library is the first of these | ||
| to be available, and a Javascript client library is planned to be released | ||
| soon. | ||
|
|
||
| For more details on customizing schema generation and using Core API | ||
| client libraries you'll need to refer to the full documentation. | ||
|
|
||
| ## Reviewing our work | ||
|
|
||
| With an incredibly small amount of code, we've now got a complete pastebin Web API, which is fully web browsable, includes a schema-driven client library, and comes complete with authentication, per-object permissions, and multiple renderer formats. | ||
|
|
||
| We've walked through each step of the design process, and seen how if we need to customize anything we can gradually work our way down to simply using regular Django views. | ||
|
|
||
| You can review the final [tutorial code][repo] on GitHub, or try out a live example in [the sandbox][sandbox]. | ||
|
|
||
| ## Onwards and upwards | ||
|
|
||
| We've reached the end of our tutorial. If you want to get more involved in the REST framework project, here are a few places you can start: | ||
|
|
||
| * Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests. | ||
| * Join the [REST framework discussion group][group], and help build the community. | ||
| * Follow [the author][twitter] on Twitter and say hi. | ||
|
|
||
| **Now go build awesome things.** | ||
|
|
||
| [coreapi]: http://www.coreapi.org | ||
| [corejson]: http://www.coreapi.org/specification/encoding/#core-json-encoding | ||
| [openapi]: https://openapis.org/ | ||
| [repo]: https://github.com/tomchristie/rest-framework-tutorial | ||
| [sandbox]: http://restframework.herokuapp.com/ | ||
| [github]: https://github.com/tomchristie/django-rest-framework | ||
| [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework | ||
| [twitter]: https://twitter.com/_tomchristie |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| markdown==2.6.4 | ||
| django-guardian==1.4.3 | ||
| django-filter==0.13.0 | ||
| coreapi==1.21.1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,9 +22,12 @@ | |
| from django.core.exceptions import ImproperlyConfigured | ||
| from django.core.urlresolvers import NoReverseMatch | ||
|
|
||
| from rest_framework import views | ||
| from rest_framework import exceptions, renderers, views | ||
| from rest_framework.compat import coreapi | ||
| from rest_framework.response import Response | ||
| from rest_framework.reverse import reverse | ||
| from rest_framework.schemas import SchemaGenerator | ||
| from rest_framework.settings import api_settings | ||
| from rest_framework.urlpatterns import format_suffix_patterns | ||
|
|
||
| Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) | ||
|
|
@@ -252,6 +255,7 @@ def get_urls(self): | |
| lookup=lookup, | ||
| trailing_slash=self.trailing_slash | ||
| ) | ||
|
|
||
| view = viewset.as_view(mapping, **route.initkwargs) | ||
| name = route.name.format(basename=basename) | ||
| ret.append(url(regex, view, name=name)) | ||
|
|
@@ -268,7 +272,11 @@ class DefaultRouter(SimpleRouter): | |
| include_format_suffixes = True | ||
| root_view_name = 'api-root' | ||
|
|
||
| def get_api_root_view(self): | ||
| def __init__(self, *args, **kwargs): | ||
| self.schema_title = kwargs.pop('schema_title', None) | ||
| super(DefaultRouter, self).__init__(*args, **kwargs) | ||
|
|
||
| def get_api_root_view(self, schema_urls=None): | ||
| """ | ||
| Return a view to use as the API root. | ||
| """ | ||
|
|
@@ -277,10 +285,24 @@ def get_api_root_view(self): | |
| for prefix, viewset, basename in self.registry: | ||
| api_root_dict[prefix] = list_name.format(basename=basename) | ||
|
|
||
| view_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES) | ||
|
|
||
| if schema_urls and self.schema_title: | ||
| assert coreapi, '`coreapi` must be installed for schema support.' | ||
| view_renderers += [renderers.CoreJSONRenderer] | ||
|
||
| schema_generator = SchemaGenerator(patterns=schema_urls) | ||
|
|
||
| class APIRoot(views.APIView): | ||
| _ignore_model_permissions = True | ||
| renderer_classes = view_renderers | ||
|
|
||
| def get(self, request, *args, **kwargs): | ||
| if request.accepted_renderer.format == 'corejson': | ||
|
||
| schema = schema_generator.get_schema(request) | ||
| if schema is None: | ||
| raise exceptions.PermissionDenied() | ||
| return Response(schema) | ||
|
|
||
| ret = OrderedDict() | ||
| namespace = request.resolver_match.namespace | ||
| for key, url_name in api_root_dict.items(): | ||
|
|
@@ -307,15 +329,13 @@ def get_urls(self): | |
| Generate the list of URL patterns, including a default root view | ||
| for the API, and appending `.json` style format suffixes. | ||
| """ | ||
| urls = [] | ||
| urls = super(DefaultRouter, self).get_urls() | ||
|
|
||
| if self.include_root_view: | ||
| root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) | ||
| view = self.get_api_root_view(schema_urls=urls) | ||
| root_url = url(r'^$', view, name=self.root_view_name) | ||
| urls.append(root_url) | ||
|
|
||
| default_urls = super(DefaultRouter, self).get_urls() | ||
| urls.extend(default_urls) | ||
|
|
||
| if self.include_format_suffixes: | ||
| urls = format_suffix_patterns(urls) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a move in 3.1 to remove features which had third-party dependencies from the core and put them into third-party packages that were still highly visible. See django-rest-framework-xml, django-rest-framework-yaml, and django-rest-framework-oauth for examples.
Is that still something we are pushing for?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda. I see
coreapias a foundational thing here, so it's a bit different.The various types of schema and docs that you can use it to generate will be third party, yup.
So eg we can have third party packages for schema formats: Swagger / API Blueprint / JSON Hyperschema, for docs templates driven by a coreapi.Document object, and for various hypermedia styles.
Using
coreapiensures that we're able to provide a common interface for all of those, so I don't have any great issues with pulling it in. If it's in core, then we're making the promise that it's an interface that is available to third-party devs, which should help drive folks making schema renderers / hypermedia renderers and docs renderers. Having said that, we could push for it to be third-party, if we wanted to, so I might be open to discussion.