Skip to content

OAS 3.0 schema generation #772

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

Merged
merged 16 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 <[email protected]>
Jerel Unruh <[email protected]>
Jonathan Senecal <[email protected]>
Joseba Mendivil <[email protected]>
Kieran Evans <[email protected]>
Léo S. <[email protected]>
Luc Cary <[email protected]>
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