mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-29 17:39:48 +03:00
add OpenAPI schema initialization
This commit is contained in:
parent
37f210a455
commit
3fd1052ef6
|
@ -327,6 +327,33 @@ May be used to pass a canonical URL for the schema.
|
||||||
url='https://www.example.org/api/'
|
url='https://www.example.org/api/'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#### `openapi_schema`
|
||||||
|
|
||||||
|
May be used to pass a static initial OpenAPI schema document, typically
|
||||||
|
containing top-level OpenAPI fields. The schema document will
|
||||||
|
be added to by the AutoSchema generator.
|
||||||
|
|
||||||
|
schema_view = get_schema_view(
|
||||||
|
openapi_schema = {
|
||||||
|
'info': {
|
||||||
|
'title': 'my title',
|
||||||
|
'version': '1.0',
|
||||||
|
'contact': {
|
||||||
|
'name': 'API Support',
|
||||||
|
'url': 'http://www.example.com/support',
|
||||||
|
'email': 'support@example.com'
|
||||||
|
},
|
||||||
|
'license': {
|
||||||
|
'name': 'Apache 2.0',
|
||||||
|
'url': 'https://www.apache.org/licenses/LICENSE-2.0.html'
|
||||||
|
}.
|
||||||
|
'servers': [
|
||||||
|
{'url': 'https://api.example.com'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#### `urlconf`
|
#### `urlconf`
|
||||||
|
|
||||||
A string representing the import path to the URL conf that you want
|
A string representing the import path to the URL conf that you want
|
||||||
|
|
|
@ -28,7 +28,7 @@ from .coreapi import AutoSchema, ManualSchema, SchemaGenerator # noqa
|
||||||
|
|
||||||
|
|
||||||
def get_schema_view(
|
def get_schema_view(
|
||||||
title=None, url=None, description=None, urlconf=None, renderer_classes=None,
|
title=None, url=None, description=None, openapi_schema=None, urlconf=None, renderer_classes=None,
|
||||||
public=False, patterns=None, generator_class=None,
|
public=False, patterns=None, generator_class=None,
|
||||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||||
|
@ -41,10 +41,17 @@ def get_schema_view(
|
||||||
else:
|
else:
|
||||||
generator_class = openapi.SchemaGenerator
|
generator_class = openapi.SchemaGenerator
|
||||||
|
|
||||||
generator = generator_class(
|
if isinstance(generator_class, openapi.SchemaGenerator):
|
||||||
title=title, url=url, description=description,
|
generator = generator_class(
|
||||||
urlconf=urlconf, patterns=patterns,
|
title=title, url=url, description=description,
|
||||||
)
|
urlconf=urlconf, patterns=patterns,
|
||||||
|
openapi_schema=openapi_schema,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
generator = generator_class(
|
||||||
|
title=title, url=url, description=description,
|
||||||
|
urlconf=urlconf, patterns=patterns,
|
||||||
|
)
|
||||||
|
|
||||||
# Avoid import cycle on APIView
|
# Avoid import cycle on APIView
|
||||||
from .views import SchemaView
|
from .views import SchemaView
|
||||||
|
|
|
@ -163,7 +163,7 @@ class BaseSchemaGenerator(object):
|
||||||
# Set by 'SCHEMA_COERCE_PATH_PK'.
|
# Set by 'SCHEMA_COERCE_PATH_PK'.
|
||||||
coerce_path_pk = None
|
coerce_path_pk = None
|
||||||
|
|
||||||
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None):
|
def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None, **kwargs):
|
||||||
if url and not url.endswith('/'):
|
if url and not url.endswith('/'):
|
||||||
url += '/'
|
url += '/'
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ from .utils import get_pk_description, is_list_view
|
||||||
|
|
||||||
|
|
||||||
class SchemaGenerator(BaseSchemaGenerator):
|
class SchemaGenerator(BaseSchemaGenerator):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
#: the openapi schema document:
|
||||||
|
self.openapi_schema = {}
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
info = {
|
info = {
|
||||||
|
@ -43,6 +47,9 @@ class SchemaGenerator(BaseSchemaGenerator):
|
||||||
subpath = '/' + path[len(prefix):]
|
subpath = '/' + path[len(prefix):]
|
||||||
result.setdefault(subpath, {})
|
result.setdefault(subpath, {})
|
||||||
result[subpath][method.lower()] = operation
|
result[subpath][method.lower()] = operation
|
||||||
|
if hasattr(view.schema, 'openapi_schema'):
|
||||||
|
# TODO: shallow or deep merge?
|
||||||
|
self.openapi_schema = {**self.openapi_schema, **view.schema.openapi_schema}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -61,13 +68,19 @@ class SchemaGenerator(BaseSchemaGenerator):
|
||||||
'info': self.get_info(),
|
'info': self.get_info(),
|
||||||
'paths': paths,
|
'paths': paths,
|
||||||
}
|
}
|
||||||
|
# TODO: shallow or deep merge?
|
||||||
|
self.openapi_schema = {**schema, **self.openapi_schema}
|
||||||
|
|
||||||
return schema
|
return self.openapi_schema
|
||||||
|
|
||||||
# View Inspectors
|
# View Inspectors
|
||||||
|
|
||||||
|
|
||||||
class AutoSchema(ViewInspector):
|
class AutoSchema(ViewInspector):
|
||||||
|
def __init__(self, openapi_schema={}):
|
||||||
|
super().__init__()
|
||||||
|
# TODO: call this manual_fields ala coreapi?
|
||||||
|
self.openapi_schema = openapi_schema
|
||||||
|
|
||||||
content_types = ['application/json']
|
content_types = ['application/json']
|
||||||
method_mapping = {
|
method_mapping = {
|
||||||
|
|
|
@ -12,6 +12,11 @@ class GetSchemaViewTests(TestCase):
|
||||||
assert isinstance(schema_view.initkwargs['schema_generator'], openapi.SchemaGenerator)
|
assert isinstance(schema_view.initkwargs['schema_generator'], openapi.SchemaGenerator)
|
||||||
assert renderers.OpenAPIRenderer in schema_view.cls().renderer_classes
|
assert renderers.OpenAPIRenderer in schema_view.cls().renderer_classes
|
||||||
|
|
||||||
|
def test_openapi_initialized(self):
|
||||||
|
schema_view = get_schema_view(openapi_schema={'info': {'title': 'With OpenAPI'}})
|
||||||
|
assert isinstance(schema_view.initkwargs['schema_generator'], openapi.SchemaGenerator)
|
||||||
|
assert renderers.OpenAPIRenderer in schema_view.cls().renderer_classes
|
||||||
|
|
||||||
@pytest.mark.skipif(not coreapi.coreapi, reason='coreapi is not installed')
|
@pytest.mark.skipif(not coreapi.coreapi, reason='coreapi is not installed')
|
||||||
def test_coreapi(self):
|
def test_coreapi(self):
|
||||||
with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}):
|
with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}):
|
||||||
|
|
|
@ -226,6 +226,29 @@ class TestGenerator(TestCase):
|
||||||
|
|
||||||
assert 'openapi' in schema
|
assert 'openapi' in schema
|
||||||
assert 'paths' in schema
|
assert 'paths' in schema
|
||||||
|
assert 'info' in schema
|
||||||
|
assert 'title' in schema['info']
|
||||||
|
assert 'version' in schema['info']
|
||||||
|
assert schema['info']['title'] is None
|
||||||
|
assert schema['info']['version'] == 'TODO'
|
||||||
|
|
||||||
|
def test_schema_initializer(self):
|
||||||
|
"""Construction of top-level dictionary with an initializer."""
|
||||||
|
class MyListView(views.ExampleListView):
|
||||||
|
schema = AutoSchema(openapi_schema={'info': {'title': 'mytitle', 'version': 'myversion'}})
|
||||||
|
|
||||||
|
patterns = [
|
||||||
|
url(r'^example/?$', MyListView.as_view()),
|
||||||
|
]
|
||||||
|
generator = SchemaGenerator(patterns=patterns)
|
||||||
|
|
||||||
|
request = create_request('/')
|
||||||
|
schema = generator.get_schema(request=request)
|
||||||
|
|
||||||
|
assert 'info' in schema
|
||||||
|
assert 'title' in schema['info']
|
||||||
|
assert 'version' in schema['info']
|
||||||
|
assert schema['info']['title'] == 'mytitle' and schema['info']['version'] == 'myversion'
|
||||||
|
|
||||||
def test_serializer_datefield(self):
|
def test_serializer_datefield(self):
|
||||||
patterns = [
|
patterns = [
|
||||||
|
|
Loading…
Reference in New Issue
Block a user