Add OpenAPIRenderer and generate_schema command

This commit is contained in:
Tom Christie 2018-10-03 13:19:40 +01:00
parent c9d2bbcead
commit ce1806a39e
5 changed files with 148 additions and 3 deletions

View File

@ -11,12 +11,19 @@ from django.utils import six
from django.views.generic import View
try:
# Python 3 (required for 3.8+)
# Python 3
from collections.abc import Mapping # noqa
except ImportError:
# Python 2.7
from collections import Mapping # noqa
try:
# Python 3
import urllib.parse as urlparse # noqa
except ImportError:
# Python 2.7
from urlparse import urlparse # noqa
try:
from django.urls import ( # noqa
URLPattern,

View File

View File

@ -0,0 +1,46 @@
from django.core.management.base import BaseCommand
from rest_framework.compat import coreapi
from rest_framework.renderers import CoreJSONRenderer, OpenAPIRenderer
from rest_framework.settings import api_settings
class Command(BaseCommand):
help = "Generates configured API schema for project."
def add_arguments(self, parser):
# TODO
# SchemaGenerator allows passing:
#
# - title
# - url
# - description
# - urlconf
# - patterns
#
# Don't particularly want to pass these on the command-line.
# conf file?
#
# Other options to consider:
# - indent
# - ...
pass
def handle(self, *args, **options):
assert coreapi is not None, 'coreapi must be installed.'
generator_class = api_settings.DEFAULT_SCHEMA_GENERATOR_CLASS()
generator = generator_class()
schema = generator.get_schema(request=None, public=True)
renderer = self.get_renderer('openapi')
output = renderer.render(schema)
self.stdout.write(output)
def get_renderer(self, format):
return {
'corejson': CoreJSONRenderer(),
'openapi': OpenAPIRenderer()
}

View File

@ -24,8 +24,8 @@ from django.utils.html import mark_safe
from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi,
pygments_css
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
pygments_css, urlparse
)
from rest_framework.exceptions import ParseError
from rest_framework.request import is_form_media_type, override_method
@ -932,3 +932,95 @@ class CoreJSONRenderer(BaseRenderer):
indent = bool(renderer_context.get('indent', 0))
codec = coreapi.codecs.CoreJSONCodec()
return codec.dump(data, indent=indent)
class OpenAPIRenderer:
CLASS_TO_TYPENAME = {
coreschema.Object: 'object',
coreschema.Array: 'array',
coreschema.Number: 'number',
coreschema.Integer: 'integer',
coreschema.String: 'string',
coreschema.Boolean: 'boolean',
}
def __init__(self):
assert coreapi, 'Using OpenAPIRenderer, but `coreapi` is not installed.'
def get_schema(self, instance):
schema = {}
if instance.__class__ in self.CLASS_TO_TYPENAME:
schema['type'] = self.CLASS_TO_TYPENAME[instance.__class__]
schema['title'] = instance.title,
schema['description'] = instance.description
if hasattr(instance, 'enum'):
schema['enum'] = instance.enum
return schema
def get_parameters(self, link):
parameters = []
for field in link.fields:
if field.location not in ['path', 'query']:
continue
parameter = {
'name': field.name,
'in': field.location,
}
if field.required:
parameter['required'] = True
if field.description:
parameter['description'] = field.description
if field.schema:
parameter['schema'] = self.get_schema(field.schema)
parameters.append(parameter)
return parameters
def get_operation(self, link, name, tag):
operation_id = "%s_%s" % (tag, name) if tag else name
parameters = self.get_parameters(link)
operation = {
'operationId': operation_id,
}
if link.title:
operation['summary'] = link.title
if link.description:
operation['description'] = link.description
if parameters:
operation['parameters'] = parameters
if tag:
operation['tags'] = [tag]
return operation
def get_paths(self, document):
paths = {}
tag = None
for name, link in document.links.items():
path = urlparse.urlparse(link.url).path
method = link.action.lower()
paths.setdefault(path, {})
paths[path][method] = self.get_operation(link, name, tag=tag)
for tag, section in document.data.items():
for name, link in section.links.items():
path = urlparse.urlparse(link.url).path
method = link.action.lower()
paths.setdefault(path, {})
paths[path][method] = self.get_operation(link, name, tag=tag)
return paths
def render(self, data, media_type=None, renderer_context=None):
return json.dumps({
'openapi': '3.0.0',
'info': {
'version': '',
'title': data.title,
'description': data.description
},
'servers': [{
'url': data.url
}],
'paths': self.get_paths(data)
}, indent=4)