Skip to content

Commit 9113ca0

Browse files
keyz182n2ygksliverc
authored
OAS 3.0 schema generation (#772)
initial implementation of OAS 3.0 generateschema Co-authored-by: Alan Crosswell <[email protected]> Co-authored-by: Oliver Sauder <[email protected]>
1 parent b192cb0 commit 9113ca0

18 files changed

+1910
-7
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Jason Housley <[email protected]>
1414
Jerel Unruh <[email protected]>
1515
Jonathan Senecal <[email protected]>
1616
Joseba Mendivil <[email protected]>
17+
Kieran Evans <[email protected]>
1718
1819
1920
Matt Layman <https://www.mattlayman.com>

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,17 @@ This release is not backwards compatible. For easy migration best upgrade first
1818
* Added support for Django REST framework 3.12
1919
* Added support for Django 3.1
2020
* Added support for Python 3.9
21+
* Added initial optional support for [openapi](https://www.openapis.org/) schema generation. Enable with:
22+
```
23+
pip install djangorestframework-jsonapi['openapi']
24+
```
25+
This first release is a start at implementing OAS schema generation. To use the generated schema you may
26+
still need to manually add some schema attributes but can expect future improvements here and as
27+
upstream DRF's OAS schema generation continues to mature.
2128

2229
### Removed
2330

31+
2432
* Removed support for Python 3.5.
2533
* Removed support for Django 1.11.
2634
* Removed support for Django 2.1.

README.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ From PyPI
108108
$ # for optional package integrations
109109
$ pip install djangorestframework-jsonapi['django-filter']
110110
$ pip install djangorestframework-jsonapi['django-polymorphic']
111+
$ pip install djangorestframework-jsonapi['openapi']
111112

112113

113114
From Source
@@ -135,7 +136,10 @@ installed and activated:
135136
$ django-admin loaddata drf_example --settings=example.settings
136137
$ django-admin runserver --settings=example.settings
137138

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

140144

141145
Running Tests and linting

docs/getting-started.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ From PyPI
6767
# for optional package integrations
6868
pip install djangorestframework-jsonapi['django-filter']
6969
pip install djangorestframework-jsonapi['django-polymorphic']
70+
pip install djangorestframework-jsonapi['openapi']
7071

7172
From Source
7273

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

8788

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

9094
## Running Tests
9195

docs/usage.md

+122-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Usage
32

43
The DJA package implements a custom renderer, parser, exception handler, query filter backends, and
@@ -32,6 +31,7 @@ REST_FRAMEWORK = {
3231
'rest_framework.renderers.BrowsableAPIRenderer'
3332
),
3433
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
34+
'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
3535
'DEFAULT_FILTER_BACKENDS': (
3636
'rest_framework_json_api.filters.QueryParameterValidationFilter',
3737
'rest_framework_json_api.filters.OrderingFilter',
@@ -944,3 +944,124 @@ The `prefetch_related` case will issue 4 queries, but they will be small and fas
944944
### Relationships
945945
### Errors
946946
-->
947+
948+
## Generating an OpenAPI Specification (OAS) 3.0 schema document
949+
950+
DRF >= 3.12 has a [new OAS schema functionality](https://www.django-rest-framework.org/api-guide/schemas/) to generate an
951+
[OAS 3.0 schema](https://www.openapis.org/) as a YAML or JSON file.
952+
953+
DJA extends DRF's schema support to generate an OAS schema in the JSON:API format.
954+
955+
### AutoSchema Settings
956+
957+
In order to produce an OAS schema that properly represents the JSON:API structure
958+
you have to either add a `schema` attribute to each view class or set the `REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS']`
959+
to DJA's version of AutoSchema.
960+
961+
#### View-based
962+
963+
```python
964+
from rest_framework_json_api.schemas.openapi import AutoSchema
965+
966+
class MyViewset(ModelViewSet):
967+
schema = AutoSchema
968+
...
969+
```
970+
971+
#### Default schema class
972+
973+
```python
974+
REST_FRAMEWORK = {
975+
# ...
976+
'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
977+
}
978+
```
979+
980+
### Adding additional OAS schema content
981+
982+
You can extend the OAS schema document by subclassing
983+
[`SchemaGenerator`](https://www.django-rest-framework.org/api-guide/schemas/#schemagenerator)
984+
and extending `get_schema`.
985+
986+
987+
Here's an example that adds OAS `info` and `servers` objects.
988+
989+
```python
990+
from rest_framework_json_api.schemas.openapi import SchemaGenerator as JSONAPISchemaGenerator
991+
992+
993+
class MySchemaGenerator(JSONAPISchemaGenerator):
994+
"""
995+
Describe my OAS schema info in detail (overriding what DRF put in) and list the servers where it can be found.
996+
"""
997+
def get_schema(self, request, public):
998+
schema = super().get_schema(request, public)
999+
schema['info'] = {
1000+
'version': '1.0',
1001+
'title': 'my demo API',
1002+
'description': 'A demonstration of [OAS 3.0](https://www.openapis.org)',
1003+
'contact': {
1004+
'name': 'my name'
1005+
},
1006+
'license': {
1007+
'name': 'BSD 2 clause',
1008+
'url': 'https://github.com/django-json-api/django-rest-framework-json-api/blob/master/LICENSE',
1009+
}
1010+
}
1011+
schema['servers'] = [
1012+
{'url': 'https://localhost/v1', 'description': 'local docker'},
1013+
{'url': 'http://localhost:8000/v1', 'description': 'local dev'},
1014+
{'url': 'https://api.example.com/v1', 'description': 'demo server'},
1015+
{'url': '{serverURL}', 'description': 'provide your server URL',
1016+
'variables': {'serverURL': {'default': 'http://localhost:8000/v1'}}}
1017+
]
1018+
return schema
1019+
```
1020+
1021+
### Generate a Static Schema on Command Line
1022+
1023+
See [DRF documentation for generateschema](https://www.django-rest-framework.org/api-guide/schemas/#generating-a-static-schema-with-the-generateschema-management-command)
1024+
To generate an OAS schema document, use something like:
1025+
1026+
```text
1027+
$ django-admin generateschema --settings=example.settings \
1028+
--generator_class myapp.views.MySchemaGenerator >myschema.yaml
1029+
```
1030+
1031+
You can then use any number of OAS tools such as
1032+
[swagger-ui-watcher](https://www.npmjs.com/package/swagger-ui-watcher)
1033+
to render the schema:
1034+
```text
1035+
$ swagger-ui-watcher myschema.yaml
1036+
```
1037+
1038+
Note: Swagger-ui-watcher will complain that "DELETE operations cannot have a requestBody"
1039+
but it will still work. This [error](https://github.com/OAI/OpenAPI-Specification/pull/2117)
1040+
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)
1041+
is published.
1042+
1043+
([swagger-ui](https://www.npmjs.com/package/swagger-ui) will work silently.)
1044+
1045+
### Generate a Dynamic Schema in a View
1046+
1047+
See [DRF documentation for a Dynamic Schema](https://www.django-rest-framework.org/api-guide/schemas/#generating-a-dynamic-schema-with-schemaview).
1048+
1049+
```python
1050+
from rest_framework.schemas import get_schema_view
1051+
1052+
urlpatterns = [
1053+
...
1054+
path('openapi', get_schema_view(
1055+
title="Example API",
1056+
description="API for all things …",
1057+
version="1.0.0",
1058+
generator_class=MySchemaGenerator,
1059+
), name='openapi-schema'),
1060+
path('swagger-ui/', TemplateView.as_view(
1061+
template_name='swagger-ui.html',
1062+
extra_context={'schema_url': 'openapi-schema'}
1063+
), name='swagger-ui'),
1064+
...
1065+
]
1066+
```
1067+

example/serializers.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,16 @@ class AuthorSerializer(serializers.ModelSerializer):
230230
queryset=Comment.objects,
231231
many=True
232232
)
233+
secrets = serializers.HiddenField(
234+
default='Shhhh!'
235+
)
236+
defaults = serializers.CharField(
237+
default='default',
238+
max_length=20,
239+
min_length=3,
240+
write_only=True,
241+
help_text='help for defaults',
242+
)
233243
included_serializers = {
234244
'bio': AuthorBioSerializer,
235245
'type': AuthorTypeSerializer
@@ -244,7 +254,8 @@ class AuthorSerializer(serializers.ModelSerializer):
244254

245255
class Meta:
246256
model = Author
247-
fields = ('name', 'email', 'bio', 'entries', 'comments', 'first_entry', 'type')
257+
fields = ('name', 'email', 'bio', 'entries', 'comments', 'first_entry', 'type',
258+
'secrets', 'defaults')
248259

249260
def get_first_entry(self, obj):
250261
return obj.entries.first()

example/settings/dev.py

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'django.contrib.sites',
2222
'django.contrib.sessions',
2323
'django.contrib.auth',
24+
'rest_framework_json_api',
2425
'rest_framework',
2526
'polymorphic',
2627
'example',
@@ -88,6 +89,7 @@
8889
'rest_framework.renderers.BrowsableAPIRenderer',
8990
),
9091
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
92+
'DEFAULT_SCHEMA_CLASS': 'rest_framework_json_api.schemas.openapi.AutoSchema',
9193
'DEFAULT_FILTER_BACKENDS': (
9294
'rest_framework_json_api.filters.OrderingFilter',
9395
'rest_framework_json_api.django_filters.DjangoFilterBackend',

example/templates/swagger-ui.html

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Swagger</title>
5+
<meta charset="utf-8"/>
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" />
8+
</head>
9+
<body>
10+
<div id="swagger-ui"></div>
11+
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
12+
<script>
13+
const ui = SwaggerUIBundle({
14+
url: "{% url schema_url %}",
15+
dom_id: '#swagger-ui',
16+
presets: [
17+
SwaggerUIBundle.presets.apis,
18+
SwaggerUIBundle.SwaggerUIStandalonePreset
19+
],
20+
layout: "BaseLayout",
21+
requestInterceptor: (request) => {
22+
request.headers['X-CSRFToken'] = "{{ csrf_token }}"
23+
return request;
24+
}
25+
})
26+
</script>
27+
</body>
28+
</html>

0 commit comments

Comments
 (0)