Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bb0db35

Browse files
committed
Allow generateschema to handle CoreAPI & OpenAPI.
1 parent c23a1d1 commit bb0db35

File tree

4 files changed

+138
-20
lines changed

4 files changed

+138
-20
lines changed
Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,60 @@
11
from django.core.management.base import BaseCommand
22

3-
from rest_framework.compat import yaml
3+
from rest_framework import renderers
4+
from rest_framework.schemas import coreapi
45
from rest_framework.schemas.openapi import SchemaGenerator
5-
from rest_framework.utils import json
6+
from rest_framework.settings import api_settings
7+
8+
OPENAPI_MODE = 'openapi'
9+
COREAPI_MODE = 'coreapi'
610

711

812
class Command(BaseCommand):
913
help = "Generates configured API schema for project."
1014

15+
def get_mode(self):
16+
default_schema_class = api_settings.DEFAULT_SCHEMA_CLASS
17+
if issubclass(default_schema_class, coreapi.AutoSchema):
18+
return COREAPI_MODE
19+
return OPENAPI_MODE
20+
1121
def add_arguments(self, parser):
1222
parser.add_argument('--title', dest="title", default='', type=str)
1323
parser.add_argument('--url', dest="url", default=None, type=str)
1424
parser.add_argument('--description', dest="description", default=None, type=str)
15-
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json'], default='openapi', type=str)
25+
if self.get_mode() == COREAPI_MODE:
26+
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json', 'corejson'], default='openapi', type=str)
27+
else:
28+
parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json'], default='openapi', type=str)
1629

1730
def handle(self, *args, **options):
18-
generator = SchemaGenerator(
31+
generator_class = self.get_generator_class()
32+
generator = generator_class(
1933
url=options['url'],
2034
title=options['title'],
2135
description=options['description']
2236
)
23-
2437
schema = generator.get_schema(request=None, public=True)
25-
26-
# TODO: Handle via renderer? More options?
27-
if options['format'] == 'openapi':
28-
output = yaml.dump(schema, default_flow_style=False)
29-
else:
30-
output = json.dumps(schema, indent=2)
31-
32-
self.stdout.write(output)
38+
renderer = self.get_renderer(options['format'])
39+
output = renderer.render(schema, renderer_context={})
40+
self.stdout.write(output.decode('utf-8'))
41+
42+
def get_renderer(self, format):
43+
if self.get_mode() == COREAPI_MODE:
44+
renderer_cls = {
45+
'corejson': renderers.CoreJSONRenderer,
46+
'openapi': renderers.CoreAPIOpenAPIRenderer,
47+
'openapi-json': renderers.CoreAPIJSONOpenAPIRenderer,
48+
}[format]
49+
return renderer_cls()
50+
51+
renderer_cls = {
52+
'openapi': renderers.OpenAPIRenderer,
53+
'openapi-json': renderers.JSONOpenAPIRenderer,
54+
}[format]
55+
return renderer_cls()
56+
57+
def get_generator_class(self):
58+
if self.get_mode() == COREAPI_MODE:
59+
return coreapi.SchemaGenerator
60+
return SchemaGenerator

‎rest_framework/renderers.py‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,28 +1024,49 @@ def get_structure(self, data):
10241024
}
10251025

10261026

1027-
class OpenAPIRenderer(_BaseOpenAPIRenderer):
1027+
class CoreAPIOpenAPIRenderer(_BaseOpenAPIRenderer):
10281028
media_type = 'application/vnd.oai.openapi'
10291029
charset = None
10301030
format = 'openapi'
10311031

10321032
def __init__(self):
1033-
assert coreapi, 'Using OpenAPIRenderer, but `coreapi` is not installed.'
1034-
assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.'
1033+
assert coreapi, 'Using CoreAPIOpenAPIRenderer, but `coreapi` is not installed.'
1034+
assert yaml, 'Using CoreAPIOpenAPIRenderer, but `pyyaml` is not installed.'
10351035

10361036
def render(self, data, media_type=None, renderer_context=None):
10371037
structure = self.get_structure(data)
10381038
return yaml.dump(structure, default_flow_style=False).encode('utf-8')
10391039

10401040

1041-
class JSONOpenAPIRenderer(_BaseOpenAPIRenderer):
1041+
class CoreAPIJSONOpenAPIRenderer(_BaseOpenAPIRenderer):
10421042
media_type = 'application/vnd.oai.openapi+json'
10431043
charset = None
10441044
format = 'openapi-json'
10451045

10461046
def __init__(self):
1047-
assert coreapi, 'Using JSONOpenAPIRenderer, but `coreapi` is not installed.'
1047+
assert coreapi, 'Using CoreAPIJSONOpenAPIRenderer, but `coreapi` is not installed.'
10481048

10491049
def render(self, data, media_type=None, renderer_context=None):
10501050
structure = self.get_structure(data)
10511051
return json.dumps(structure, indent=4).encode('utf-8')
1052+
1053+
1054+
class OpenAPIRenderer(BaseRenderer):
1055+
media_type = 'application/vnd.oai.openapi'
1056+
charset = None
1057+
format = 'openapi'
1058+
1059+
def __init__(self):
1060+
assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.'
1061+
1062+
def render(self, data, media_type=None, renderer_context=None):
1063+
return yaml.dump(data, default_flow_style=False).encode('utf-8')
1064+
1065+
1066+
class JSONOpenAPIRenderer(BaseRenderer):
1067+
media_type = 'application/vnd.oai.openapi+json'
1068+
charset = None
1069+
format = 'openapi-json'
1070+
1071+
def render(self, data, media_type=None, renderer_context=None):
1072+
return json.dumps(data, indent=2).encode('utf-8')

‎rest_framework/schemas/views.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def __init__(self, *args, **kwargs):
2020
super(SchemaView, self).__init__(*args, **kwargs)
2121
if self.renderer_classes is None:
2222
self.renderer_classes = [
23-
renderers.OpenAPIRenderer,
23+
renderers.CoreAPIOpenAPIRenderer,
2424
renderers.CoreJSONRenderer
2525
]
2626
if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:

‎tests/schemas/test_managementcommand.py‎

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from django.utils import six
99

1010
from rest_framework.compat import uritemplate, yaml
11-
from rest_framework.utils import json
11+
from rest_framework.management.commands import generateschema
12+
from rest_framework.utils import formatting, json
1213
from rest_framework.views import APIView
1314

1415

@@ -30,6 +31,13 @@ class GenerateSchemaTests(TestCase):
3031
def setUp(self):
3132
self.out = six.StringIO()
3233

34+
def test_command_detects_schema_generation_mode(self):
35+
"""Switching between CoreAPI & OpenAPI"""
36+
command = generateschema.Command()
37+
assert command.get_mode() == generateschema.OPENAPI_MODE
38+
with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}):
39+
assert command.get_mode() == generateschema.COREAPI_MODE
40+
3341
@pytest.mark.skipif(six.PY2, reason='PyYAML unicode output is malformed on PY2.')
3442
@pytest.mark.skipif(yaml is None, reason='PyYAML is required.')
3543
def test_renders_default_schema_with_custom_title_url_and_description(self):
@@ -49,3 +57,64 @@ def test_renders_openapi_json_schema(self):
4957
# Check valid JSON was output.
5058
out_json = json.loads(self.out.getvalue())
5159
assert out_json['openapi'] == '3.0.2'
60+
61+
@pytest.mark.skipif(six.PY2, reason='PyYAML unicode output is malformed on PY2.')
62+
@pytest.mark.skipif(yaml is None, reason='PyYAML is required.')
63+
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'})
64+
def test_coreapi_renders_default_schema_with_custom_title_url_and_description(self):
65+
expected_out = """info:
66+
description: Sample description
67+
title: SampleAPI
68+
version: ''
69+
openapi: 3.0.0
70+
paths:
71+
/:
72+
get:
73+
operationId: list
74+
servers:
75+
- url: http://api.sample.com/
76+
"""
77+
call_command('generateschema',
78+
'--title=SampleAPI',
79+
'--url=http://api.sample.com',
80+
'--description=Sample description',
81+
stdout=self.out)
82+
83+
self.assertIn(formatting.dedent(expected_out), self.out.getvalue())
84+
85+
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'})
86+
def test_coreapi_renders_openapi_json_schema(self):
87+
expected_out = {
88+
"openapi": "3.0.0",
89+
"info": {
90+
"version": "",
91+
"title": "",
92+
"description": ""
93+
},
94+
"servers": [
95+
{
96+
"url": ""
97+
}
98+
],
99+
"paths": {
100+
"/": {
101+
"get": {
102+
"operationId": "list"
103+
}
104+
}
105+
}
106+
}
107+
call_command('generateschema',
108+
'--format=openapi-json',
109+
stdout=self.out)
110+
out_json = json.loads(self.out.getvalue())
111+
112+
self.assertDictEqual(out_json, expected_out)
113+
114+
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'})
115+
def test_renders_corejson_schema(self):
116+
expected_out = """{"_type":"document","":{"list":{"_type":"link","url":"/","action":"get"}}}"""
117+
call_command('generateschema',
118+
'--format=corejson',
119+
stdout=self.out)
120+
self.assertIn(expected_out, self.out.getvalue())

0 commit comments

Comments
 (0)