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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy