Rewrite schema generation (#1)
* Completeley rewritten schema generation * Added support for python 2.7 and 3.4 * Restructured testing and build configuration * Added nested request schemas This rewrite completely replaces the public interface of the django rest schema generation library, so further changes will be needed to re-enable and further extend the customization points one might want.
This commit is contained in:
+28
-2
@@ -1,13 +1,14 @@
|
||||
import pytest
|
||||
from ruamel import yaml
|
||||
|
||||
from drf_swagger import openapi, codecs
|
||||
from drf_swagger.generators import OpenAPISchemaGenerator
|
||||
from ruamel import yaml
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def generator():
|
||||
return OpenAPISchemaGenerator(
|
||||
info=openapi.Info("Test generator", "v1"),
|
||||
info=openapi.Info(title="Test generator", default_version="v1"),
|
||||
version="v2",
|
||||
)
|
||||
|
||||
@@ -27,3 +28,28 @@ def swagger_dict():
|
||||
swagger = generator().get_schema(None, True)
|
||||
json_bytes = codec_yaml().encode(swagger)
|
||||
return yaml.safe_load(json_bytes.decode('utf-8'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def validate_schema():
|
||||
def validate_schema(swagger):
|
||||
from flex.core import parse as validate_flex
|
||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||
|
||||
validate_flex(swagger)
|
||||
validate_ssv(swagger)
|
||||
|
||||
return validate_schema
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bad_settings():
|
||||
from drf_swagger.app_settings import swagger_settings, SWAGGER_DEFAULTS
|
||||
bad_security = {
|
||||
'bad': {
|
||||
'bad_attribute': 'should not be accepted'
|
||||
}
|
||||
}
|
||||
SWAGGER_DEFAULTS['SECURITY_DEFINITIONS'].update(bad_security)
|
||||
yield swagger_settings
|
||||
del SWAGGER_DEFAULTS['SECURITY_DEFINITIONS']['bad']
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
def test_operation_docstrings(swagger_dict):
|
||||
users_list = swagger_dict['paths']['/users/']
|
||||
assert users_list['get']['description'] == "UserList cbv classdoc"
|
||||
|
||||
users_detail = swagger_dict['paths']['/users/{id}/']
|
||||
assert users_detail['get']['description'] == "user_detail fbv docstring"
|
||||
@@ -0,0 +1,22 @@
|
||||
def test_appropriate_status_codes(swagger_dict):
|
||||
snippets_list = swagger_dict['paths']['/snippets/']
|
||||
assert '200' in snippets_list['get']['responses']
|
||||
assert '201' in snippets_list['post']['responses']
|
||||
|
||||
snippets_detail = swagger_dict['paths']['/snippets/{id}/']
|
||||
assert '200' in snippets_detail['get']['responses']
|
||||
assert '200' in snippets_detail['put']['responses']
|
||||
assert '200' in snippets_detail['patch']['responses']
|
||||
assert '204' in snippets_detail['delete']['responses']
|
||||
|
||||
|
||||
def test_operation_docstrings(swagger_dict):
|
||||
snippets_list = swagger_dict['paths']['/snippets/']
|
||||
assert snippets_list['get']['description'] == "SnippetList classdoc"
|
||||
assert snippets_list['post']['description'] == "post method docstring"
|
||||
|
||||
snippets_detail = swagger_dict['paths']['/snippets/{id}/']
|
||||
assert snippets_detail['get']['description'] == "SnippetDetail classdoc"
|
||||
assert snippets_detail['put']['description'] == "put class docstring"
|
||||
assert snippets_detail['patch']['description'] == "patch method docstring"
|
||||
assert snippets_detail['delete']['description'] == "delete method docstring"
|
||||
@@ -0,0 +1,29 @@
|
||||
def test_appropriate_status_codes(swagger_dict):
|
||||
articles_list = swagger_dict['paths']['/articles/']
|
||||
assert '200' in articles_list['get']['responses']
|
||||
assert '201' in articles_list['post']['responses']
|
||||
|
||||
articles_detail = swagger_dict['paths']['/articles/{slug}/']
|
||||
assert '200' in articles_detail['get']['responses']
|
||||
assert '200' in articles_detail['put']['responses']
|
||||
assert '200' in articles_detail['patch']['responses']
|
||||
assert '204' in articles_detail['delete']['responses']
|
||||
|
||||
|
||||
def test_operation_docstrings(swagger_dict):
|
||||
articles_list = swagger_dict['paths']['/articles/']
|
||||
assert articles_list['get']['description'] == "ArticleViewSet class docstring"
|
||||
assert articles_list['post']['description'] == "ArticleViewSet class docstring"
|
||||
|
||||
articles_detail = swagger_dict['paths']['/articles/{slug}/']
|
||||
assert articles_detail['get']['description'] == "retrieve class docstring"
|
||||
assert articles_detail['put']['description'] == "update method docstring"
|
||||
assert articles_detail['patch']['description'] == "ArticleViewSet class docstring"
|
||||
assert articles_detail['delete']['description'] == "destroy method docstring"
|
||||
|
||||
articles_today = swagger_dict['paths']['/articles/today/']
|
||||
assert articles_today['get']['description'] == "ArticleViewSet class docstring"
|
||||
|
||||
articles_image = swagger_dict['paths']['/articles/{slug}/image/']
|
||||
assert articles_image['get']['description'] == "image method docstring"
|
||||
assert articles_image['post']['description'] == "image method docstring"
|
||||
@@ -1,17 +1,10 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from drf_swagger import openapi, codecs
|
||||
from drf_swagger.generators import OpenAPISchemaGenerator
|
||||
from ruamel import yaml
|
||||
|
||||
|
||||
def validate_schema(swagger):
|
||||
from flex.core import parse as validate_flex
|
||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||
|
||||
validate_flex(swagger)
|
||||
validate_ssv(swagger)
|
||||
from drf_swagger import openapi, codecs
|
||||
from drf_swagger.generators import OpenAPISchemaGenerator
|
||||
|
||||
|
||||
def test_schema_generates_without_errors(generator):
|
||||
@@ -24,9 +17,10 @@ def test_schema_is_valid(generator, codec_yaml):
|
||||
|
||||
|
||||
def test_invalid_schema_fails(codec_json):
|
||||
# noinspection PyTypeChecker
|
||||
bad_generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(
|
||||
"Test generator", "v1",
|
||||
title="Test generator", default_version="v1",
|
||||
contact=openapi.Contact(name=69, email=[])
|
||||
),
|
||||
version="v2",
|
||||
@@ -37,13 +31,13 @@ def test_invalid_schema_fails(codec_json):
|
||||
codec_json.encode(swagger)
|
||||
|
||||
|
||||
def test_json_codec_roundtrip(codec_json, generator):
|
||||
def test_json_codec_roundtrip(codec_json, generator, validate_schema):
|
||||
swagger = generator.get_schema(None, True)
|
||||
json_bytes = codec_json.encode(swagger)
|
||||
validate_schema(json.loads(json_bytes.decode('utf-8')))
|
||||
|
||||
|
||||
def test_yaml_codec_roundtrip(codec_yaml, generator):
|
||||
def test_yaml_codec_roundtrip(codec_yaml, generator, validate_schema):
|
||||
swagger = generator.get_schema(None, True)
|
||||
json_bytes = codec_yaml.encode(swagger)
|
||||
validate_schema(yaml.safe_load(json_bytes.decode('utf-8')))
|
||||
|
||||
@@ -1,24 +1,2 @@
|
||||
def test_paths_not_empty(swagger_dict):
|
||||
assert bool(swagger_dict['paths'])
|
||||
|
||||
|
||||
def test_appropriate_status_codes(swagger_dict):
|
||||
snippets_list = swagger_dict['paths']['/snippets/']
|
||||
assert '200' in snippets_list['get']['responses']
|
||||
assert '201' in snippets_list['post']['responses']
|
||||
snippets_detail = swagger_dict['paths']['/snippets/{id}/']
|
||||
assert '200' in snippets_detail['get']['responses']
|
||||
assert '200' in snippets_detail['put']['responses']
|
||||
assert '200' in snippets_detail['patch']['responses']
|
||||
assert '204' in snippets_detail['delete']['responses']
|
||||
|
||||
|
||||
def test_operation_docstrings(swagger_dict):
|
||||
snippets_list = swagger_dict['paths']['/snippets/']
|
||||
assert snippets_list['get']['description'] == "SnippetList classdoc"
|
||||
assert snippets_list['post']['description'] == "post method docstring"
|
||||
snippets_detail = swagger_dict['paths']['/snippets/{id}/']
|
||||
assert snippets_detail['get']['description'] == "SnippetDetail classdoc"
|
||||
assert snippets_detail['put']['description'] == "put class docstring"
|
||||
assert snippets_detail['patch']['description'] == "patch method docstring"
|
||||
assert snippets_detail['delete']['description'] == "delete method docstring"
|
||||
assert len(swagger_dict['paths']) > 0
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import json
|
||||
|
||||
from ruamel import yaml
|
||||
|
||||
|
||||
def _validate_text_schema_view(client, validate_schema, path, loader):
|
||||
response = client.get(path)
|
||||
assert response.status_code == 200
|
||||
validate_schema(loader(response.content.decode('utf-8')))
|
||||
|
||||
|
||||
def _validate_ui_schema_view(client, path, string):
|
||||
response = client.get(path)
|
||||
assert response.status_code == 200
|
||||
assert string in response.content.decode('utf-8')
|
||||
|
||||
|
||||
def test_swagger_json(client, validate_schema):
|
||||
_validate_text_schema_view(client, validate_schema, "/swagger.json", json.loads)
|
||||
|
||||
|
||||
def test_swagger_yaml(client, validate_schema):
|
||||
_validate_text_schema_view(client, validate_schema, "/swagger.yaml", yaml.safe_load)
|
||||
|
||||
|
||||
def test_exception_middleware(client, bad_settings):
|
||||
response = client.get('/swagger.json')
|
||||
assert response.status_code == 500
|
||||
assert 'errors' in json.loads(response.content.decode('utf-8'))
|
||||
|
||||
|
||||
def test_swagger_ui(client, validate_schema):
|
||||
_validate_ui_schema_view(client, '/swagger/', 'swagger-ui-dist/swagger-ui-bundle.js')
|
||||
_validate_text_schema_view(client, validate_schema, '/swagger/?format=openapi', json.loads)
|
||||
|
||||
|
||||
def test_redoc(client, validate_schema):
|
||||
_validate_ui_schema_view(client, '/redoc/', 'redoc/redoc.min.js')
|
||||
_validate_text_schema_view(client, validate_schema, '/redoc/?format=openapi', json.loads)
|
||||
@@ -0,0 +1,53 @@
|
||||
from drf_swagger import openapi
|
||||
|
||||
|
||||
def test_vendor_extensions():
|
||||
"""Any attribute starting with x_ should map to a vendor property of the form x-camelCase"""
|
||||
sd = openapi.SwaggerDict(x_vendor_ext_1='test')
|
||||
sd.x_vendor_ext_2 = 'test'
|
||||
assert 'x-vendorExt1' in sd
|
||||
assert sd.x_vendor_ext_1 == 'test'
|
||||
assert sd['x-vendorExt2'] == 'test'
|
||||
|
||||
del sd.x_vendor_ext_1
|
||||
assert 'x-vendorExt1' not in sd
|
||||
|
||||
|
||||
def test_ref():
|
||||
"""The attribute 'ref' maps to the swagger key '$ref'"""
|
||||
sd = openapi.SwaggerDict(ref='reftest')
|
||||
assert '$ref' in sd
|
||||
assert sd['$ref'] == sd.ref == 'reftest'
|
||||
|
||||
del sd['$ref']
|
||||
assert not hasattr(sd, 'ref')
|
||||
|
||||
|
||||
def test_leading_underscore_ignored():
|
||||
"""Attributes with a leading underscore are set on the object as-is and are not added to its dict form"""
|
||||
sd = openapi.SwaggerDict(_private_attr_1='not_camelised')
|
||||
initial_len = len(sd)
|
||||
sd._nope = 'not camelised either'
|
||||
assert len(sd) == initial_len
|
||||
assert 'privateAttr1' not in sd
|
||||
assert sd._private_attr_1 == 'not_camelised'
|
||||
assert '_private_attr_1' not in sd
|
||||
assert hasattr(sd, '_nope')
|
||||
|
||||
del sd._nope
|
||||
assert not hasattr(sd, '_nope')
|
||||
|
||||
|
||||
def test_trailing_underscore_stripped():
|
||||
"""Trailing underscores are stripped when converting attribute names.
|
||||
This allows, for example, python keywords to function as SwaggerDict attributes."""
|
||||
sd = openapi.SwaggerDict(trailing_underscore_='trailing')
|
||||
sd.in_ = 'trailing'
|
||||
assert 'in' in sd
|
||||
assert 'trailingUnderscore' in sd
|
||||
assert sd.trailing_underscore == sd['in']
|
||||
assert hasattr(sd, 'in___')
|
||||
|
||||
del sd.in_
|
||||
assert 'in' not in sd
|
||||
assert not hasattr(sd, 'in__')
|
||||
Reference in New Issue
Block a user