Skip to content

Commit

Permalink
OAS 3.0 schema generation (#772)
Browse files Browse the repository at this point in the history
initial implementation of OAS 3.0 generateschema

Co-authored-by: Alan Crosswell <alan@columbia.edu>
Co-authored-by: Oliver Sauder <sliverc@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 30, 2020
1 parent b192cb0 commit 9113ca0
Show file tree
Hide file tree
Showing 18 changed files with 1,910 additions and 7 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Jason Housley <housleyjk@gmail.com>
Jerel Unruh <mail@unruhdesigns.com>
Jonathan Senecal <contact@jonathansenecal.com>
Joseba Mendivil <git@jma.email>
Kieran Evans <keyz182@gmail.com>
Léo S. <leo@naeka.fr>
Luc Cary <luc.cary@gmail.com>
Matt Layman <https://www.mattlayman.com>
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ This release is not backwards compatible. For easy migration best upgrade first
* Added support for Django REST framework 3.12
* Added support for Django 3.1
* Added support for Python 3.9
* Added initial optional support for [openapi](https://www.openapis.org/) schema generation. Enable with:
```
pip install djangorestframework-jsonapi['openapi']
```
This first release is a start at implementing OAS schema generation. To use the generated schema you may
still need to manually add some schema attributes but can expect future improvements here and as
upstream DRF's OAS schema generation continues to mature.

### Removed


* Removed support for Python 3.5.
* Removed support for Django 1.11.
* Removed support for Django 2.1.
Expand Down
6 changes: 5 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ From PyPI
$ # for optional package integrations
$ pip install djangorestframework-jsonapi['django-filter']
$ pip install djangorestframework-jsonapi['django-polymorphic']
$ pip install djangorestframework-jsonapi['openapi']


From Source
Expand Down Expand Up @@ -135,7 +136,10 @@ installed and activated:
$ django-admin loaddata drf_example --settings=example.settings
$ django-admin runserver --settings=example.settings

Browse to http://localhost:8000
Browse to
* http://localhost:8000 for the list of available collections (in a non-JSONAPI format!),
* http://localhost:8000/swagger-ui/ for a Swagger user interface to the dynamic schema view, or
* http://localhost:8000/openapi for the schema view's OpenAPI specification document.


Running Tests and linting
Expand Down
6 changes: 5 additions & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ From PyPI
# for optional package integrations
pip install djangorestframework-jsonapi['django-filter']
pip install djangorestframework-jsonapi['django-polymorphic']
pip install djangorestframework-jsonapi['openapi']

From Source

Expand All @@ -85,7 +86,10 @@ From Source
django-admin runserver --settings=example.settings


Browse to http://localhost:8000
Browse to
* [http://localhost:8000](http://localhost:8000) for the list of available collections (in a non-JSONAPI format!),
* [http://localhost:8000/swagger-ui/](http://localhost:8000/swagger-ui/) for a Swagger user interface to the dynamic schema view, or
* [http://localhost:8000/openapi](http://localhost:8000/openapi) for the schema view's OpenAPI specification document.

## Running Tests

Expand Down
123 changes: 122 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Usage

The DJA package implements a custom renderer, parser, exception handler, query filter backends, and
Expand Down Expand Up @@ -32,6 +31,7 @@ REST_FRAMEWORK = {
'rest_framework.renderers.BrowsableAPIRenderer'
),
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
'DEFAULT_FILTER_BACKENDS': (
'rest_framework_json_api.filters.QueryParameterValidationFilter',
'rest_framework_json_api.filters.OrderingFilter',
Expand Down Expand Up @@ -944,3 +944,124 @@ The `prefetch_related` case will issue 4 queries, but they will be small and fas
### Relationships
### Errors
-->

## Generating an OpenAPI Specification (OAS) 3.0 schema document

DRF >= 3.12 has a [new OAS schema functionality](https://www.django-rest-framework.org/api-guide/schemas/) to generate an
[OAS 3.0 schema](https://www.openapis.org/) as a YAML or JSON file.

DJA extends DRF's schema support to generate an OAS schema in the JSON:API format.

### AutoSchema Settings

In order to produce an OAS schema that properly represents the JSON:API structure
you have to either add a `schema` attribute to each view class or set the `REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS']`
to DJA's version of AutoSchema.

#### View-based

```python
from rest_framework_json_api.schemas.openapi import AutoSchema

class MyViewset(ModelViewSet):
schema = AutoSchema
...
```

#### Default schema class

```python
REST_FRAMEWORK = {
# ...
'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
}
```

### Adding additional OAS schema content

You can extend the OAS schema document by subclassing
[`SchemaGenerator`](https://www.django-rest-framework.org/api-guide/schemas/#schemagenerator)
and extending `get_schema`.


Here's an example that adds OAS `info` and `servers` objects.

```python
from rest_framework_json_api.schemas.openapi import SchemaGenerator as JSONAPISchemaGenerator


class MySchemaGenerator(JSONAPISchemaGenerator):
"""
Describe my OAS schema info in detail (overriding what DRF put in) and list the servers where it can be found.
"""
def get_schema(self, request, public):
schema = super().get_schema(request, public)
schema['info'] = {
'version': '1.0',
'title': 'my demo API',
'description': 'A demonstration of [OAS 3.0](https://www.openapis.org)',
'contact': {
'name': 'my name'
},
'license': {
'name': 'BSD 2 clause',
'url': 'https://github.com/django-json-api/django-rest-framework-json-api/blob/master/LICENSE',
}
}
schema['servers'] = [
{'url': 'https://localhost/v1', 'description': 'local docker'},
{'url': 'http://localhost:8000/v1', 'description': 'local dev'},
{'url': 'https://api.example.com/v1', 'description': 'demo server'},
{'url': '{serverURL}', 'description': 'provide your server URL',
'variables': {'serverURL': {'default': 'http://localhost:8000/v1'}}}
]
return schema
```

### Generate a Static Schema on Command Line

See [DRF documentation for generateschema](https://www.django-rest-framework.org/api-guide/schemas/#generating-a-static-schema-with-the-generateschema-management-command)
To generate an OAS schema document, use something like:

```text
$ django-admin generateschema --settings=example.settings \
--generator_class myapp.views.MySchemaGenerator >myschema.yaml
```

You can then use any number of OAS tools such as
[swagger-ui-watcher](https://www.npmjs.com/package/swagger-ui-watcher)
to render the schema:
```text
$ swagger-ui-watcher myschema.yaml
```

Note: Swagger-ui-watcher will complain that "DELETE operations cannot have a requestBody"
but it will still work. This [error](https://github.com/OAI/OpenAPI-Specification/pull/2117)
in the OAS specification will be fixed when [OAS 3.1.0](https://www.openapis.org/blog/2020/06/18/openapi-3-1-0-rc0-its-here)
is published.

([swagger-ui](https://www.npmjs.com/package/swagger-ui) will work silently.)

### Generate a Dynamic Schema in a View

See [DRF documentation for a Dynamic Schema](https://www.django-rest-framework.org/api-guide/schemas/#generating-a-dynamic-schema-with-schemaview).

```python
from rest_framework.schemas import get_schema_view

urlpatterns = [
...
path('openapi', get_schema_view(
title="Example API",
description="API for all things …",
version="1.0.0",
generator_class=MySchemaGenerator,
), name='openapi-schema'),
path('swagger-ui/', TemplateView.as_view(
template_name='swagger-ui.html',
extra_context={'schema_url': 'openapi-schema'}
), name='swagger-ui'),
...
]
```

13 changes: 12 additions & 1 deletion example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ class AuthorSerializer(serializers.ModelSerializer):
queryset=Comment.objects,
many=True
)
secrets = serializers.HiddenField(
default='Shhhh!'
)
defaults = serializers.CharField(
default='default',
max_length=20,
min_length=3,
write_only=True,
help_text='help for defaults',
)
included_serializers = {
'bio': AuthorBioSerializer,
'type': AuthorTypeSerializer
Expand All @@ -244,7 +254,8 @@ class AuthorSerializer(serializers.ModelSerializer):

class Meta:
model = Author
fields = ('name', 'email', 'bio', 'entries', 'comments', 'first_entry', 'type')
fields = ('name', 'email', 'bio', 'entries', 'comments', 'first_entry', 'type',
'secrets', 'defaults')

def get_first_entry(self, obj):
return obj.entries.first()
Expand Down
2 changes: 2 additions & 0 deletions example/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'django.contrib.sites',
'django.contrib.sessions',
'django.contrib.auth',
'rest_framework_json_api',
'rest_framework',
'polymorphic',
'example',
Expand Down Expand Up @@ -88,6 +89,7 @@
'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
'DEFAULT_FILTER_BACKENDS': (
'rest_framework_json_api.filters.OrderingFilter',
'rest_framework_json_api.django_filters.DjangoFilterBackend',
Expand Down
28 changes: 28 additions & 0 deletions example/templates/swagger-ui.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Swagger</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<script>
const ui = SwaggerUIBundle({
url: "{% url schema_url %}",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
requestInterceptor: (request) => {
request.headers['X-CSRFToken'] = "{{ csrf_token }}"
return request;
}
})
</script>
</body>
</html>
Loading

0 comments on commit 9113ca0

Please sign in to comment.