parent
53ac55a24b
commit
8883894775
|
|
@ -35,8 +35,8 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
|
||||
$ virtualenv venv
|
||||
$ source venv/bin/activate
|
||||
(venv) $ pip install -e .[validation,test]
|
||||
(venv) $ pip install -r requirements/dev.txt
|
||||
(venv) $ pip install -e .[validation]
|
||||
(venv) $ pip install -rrequirements/dev.txt -rrequirements/test.txt
|
||||
|
||||
#. Make your changes and check them against the test project
|
||||
|
||||
|
|
|
|||
12
README.rst
12
README.rst
|
|
@ -280,6 +280,18 @@ This method is currently the only way to get both syntactic and semantic validat
|
|||
The other validators only provide JSON schema-level validation, but miss things like duplicate operation names,
|
||||
improper content types, etc
|
||||
|
||||
5. Code generation
|
||||
==================
|
||||
|
||||
You can use the specification outputted by this library together with
|
||||
`swagger-codegen <https://github.com/swagger-api/swagger-codegen>`_ to generate client code in your language of choice:
|
||||
|
||||
.. code:: console
|
||||
|
||||
$ docker run --rm -v ${PWD}:/local swaggerapi/swagger-codegen-cli generate -i /local/tests/reference.yaml -l javascript -o /local/.codegen/js
|
||||
|
||||
See the github page linked above for more details.
|
||||
|
||||
**********
|
||||
Background
|
||||
**********
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
|
||||
"integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY="
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.6.1.tgz",
|
||||
"integrity": "sha1-uzQgV/h2COTs2DlGMDSJxjYicgY="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "drf-swagger",
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": "^3.5.0"
|
||||
"swagger-ui-dist": "^3.6.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# pytest runner + plugins
|
||||
pytest-django>=3.1.2
|
||||
pytest>=2.9
|
||||
pytest-pythonpath>=0.7.1
|
||||
pytest-cov>=2.5.1
|
||||
# latest pip version of pytest-django is more than a year old and does not support Django 2.0
|
||||
git+https://github.com/pytest-dev/pytest-django.git@94cccb956435dd7a719606744ee7608397e1eafb
|
||||
datadiff==2.0.0
|
||||
|
||||
# test project requirements
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -16,7 +16,6 @@ with io.open('README.rst', encoding='utf-8') as readme:
|
|||
|
||||
requirements = ['djangorestframework>=3.7.0'] + read_req('base.txt')
|
||||
requirements_validation = read_req('validation.txt')
|
||||
requirements_test = read_req('test.txt')
|
||||
|
||||
setup(
|
||||
name='drf-swagger',
|
||||
|
|
@ -25,10 +24,8 @@ setup(
|
|||
package_dir={'': 'src'},
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
tests_require=requirements_test,
|
||||
extras_require={
|
||||
'validation': requirements_validation,
|
||||
'test': requirements_test,
|
||||
},
|
||||
license='BSD License',
|
||||
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django.conf import settings
|
||||
from rest_framework.settings import APISettings
|
||||
from rest_framework.settings import perform_import
|
||||
|
||||
SWAGGER_DEFAULTS = {
|
||||
'USE_SESSION_AUTH': True,
|
||||
|
|
@ -30,16 +30,49 @@ REDOC_DEFAULTS = {
|
|||
|
||||
IMPORT_STRINGS = []
|
||||
|
||||
|
||||
class AppSettings(object):
|
||||
"""
|
||||
Stolen from Django Rest Framework, removed caching for easier testing
|
||||
"""
|
||||
|
||||
def __init__(self, user_settings, defaults, import_strings=None):
|
||||
self._user_settings = user_settings
|
||||
self.defaults = defaults
|
||||
self.import_strings = import_strings or []
|
||||
|
||||
@property
|
||||
def user_settings(self):
|
||||
return getattr(settings, self._user_settings, {})
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr not in self.defaults:
|
||||
raise AttributeError("Invalid setting: '%s'" % attr) # pragma: no cover
|
||||
|
||||
try:
|
||||
# Check if present in user settings
|
||||
val = self.user_settings[attr]
|
||||
except KeyError:
|
||||
# Fall back to defaults
|
||||
val = self.defaults[attr]
|
||||
|
||||
# Coerce import strings into classes
|
||||
if attr in self.import_strings:
|
||||
val = perform_import(val, attr)
|
||||
|
||||
return val
|
||||
|
||||
|
||||
#:
|
||||
swagger_settings = APISettings(
|
||||
user_settings=getattr(settings, 'SWAGGER_SETTINGS', {}),
|
||||
swagger_settings = AppSettings(
|
||||
user_settings='SWAGGER_SETTINGS',
|
||||
defaults=SWAGGER_DEFAULTS,
|
||||
import_strings=IMPORT_STRINGS,
|
||||
)
|
||||
|
||||
#:
|
||||
redoc_settings = APISettings(
|
||||
user_settings=getattr(settings, 'REDOC_SETTINGS', {}),
|
||||
redoc_settings = AppSettings(
|
||||
user_settings='REDOC_SETTINGS',
|
||||
defaults=REDOC_DEFAULTS,
|
||||
import_strings=IMPORT_STRINGS,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,6 @@ VALIDATORS = {
|
|||
class _OpenAPICodec(object):
|
||||
media_type = None
|
||||
|
||||
#: Allows easier mocking of settings
|
||||
settings = swagger_settings
|
||||
|
||||
def __init__(self, validators):
|
||||
self._validators = validators
|
||||
|
||||
|
|
@ -89,7 +86,7 @@ class _OpenAPICodec(object):
|
|||
:return: swagger spec as dict
|
||||
:rtype: OrderedDict
|
||||
"""
|
||||
swagger.security_definitions = self.settings.SECURITY_DEFINITIONS
|
||||
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
||||
return swagger
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,10 +65,10 @@ class OpenAPISchemaGenerator(object):
|
|||
view = self._gen.create_view(callback, method, request)
|
||||
overrides = getattr(callback, 'swagger_auto_schema', None)
|
||||
if overrides is not None:
|
||||
# decorated function based view must have its decorator information passed on to th re-instantiated view
|
||||
# decorated function based view must have its decorator information passed on to the re-instantiated view
|
||||
for method, _ in overrides.items():
|
||||
view_method = getattr(view, method, None)
|
||||
if view_method is not None:
|
||||
if view_method is not None: # pragma: no cover
|
||||
setattr(view_method.__func__, 'swagger_auto_schema', overrides)
|
||||
return view
|
||||
|
||||
|
|
@ -137,6 +137,7 @@ class OpenAPISchemaGenerator(object):
|
|||
schema = auto_schema_cls(view, path, method, overrides, components)
|
||||
operations[method.lower()] = schema.get_operation(operation_keys)
|
||||
|
||||
if operations:
|
||||
paths[path] = openapi.PathItem(parameters=path_parameters, **operations)
|
||||
|
||||
return openapi.Paths(paths=paths)
|
||||
|
|
@ -178,7 +179,7 @@ class OpenAPISchemaGenerator(object):
|
|||
try:
|
||||
model_field = model._meta.get_field(variable)
|
||||
except Exception:
|
||||
model_field = None
|
||||
model_field = None # pragma: no cover
|
||||
|
||||
if model_field is not None and model_field.help_text:
|
||||
description = force_text(model_field.help_text)
|
||||
|
|
|
|||
|
|
@ -166,9 +166,9 @@ class SwaggerAutoSchema(object):
|
|||
parameters = OrderedDict(((param.name, param.in_), param) for param in parameters)
|
||||
manual_parameters = self.overrides.get('manual_parameters', None) or []
|
||||
|
||||
if any(param.in_ == openapi.IN_BODY for param in manual_parameters):
|
||||
if any(param.in_ == openapi.IN_BODY for param in manual_parameters): # pragma: no cover
|
||||
raise SwaggerGenerationError("specify the body parameter as a Schema or Serializer in request_body")
|
||||
if any(param.in_ == openapi.IN_FORM for param in manual_parameters):
|
||||
if any(param.in_ == openapi.IN_FORM for param in manual_parameters): # pragma: no cover
|
||||
if any(param.in_ == openapi.IN_BODY for param in parameters.values()):
|
||||
raise SwaggerGenerationError("cannot add form parameters when the request has a request schema; "
|
||||
"did you forget to set an appropriate parser class on the view?")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from coreapi.compat import urlparse
|
|||
from future.utils import raise_from
|
||||
from inflection import camelize
|
||||
|
||||
|
||||
TYPE_OBJECT = "object" #:
|
||||
TYPE_STRING = "string" #:
|
||||
TYPE_NUMBER = "number" #:
|
||||
|
|
@ -212,7 +211,7 @@ class Paths(SwaggerDict):
|
|||
super(Paths, self).__init__(**extra)
|
||||
for path, path_obj in paths.items():
|
||||
assert path.startswith("/")
|
||||
if path_obj is not None:
|
||||
if path_obj is not None: # pragma: no cover
|
||||
self[path] = path_obj
|
||||
self._insert_extras__()
|
||||
|
||||
|
|
@ -403,7 +402,7 @@ class Responses(SwaggerDict):
|
|||
"""
|
||||
super(Responses, self).__init__(**extra)
|
||||
for status, response in responses.items():
|
||||
if response is not None:
|
||||
if response is not None: # pragma: no cover
|
||||
self[str(status)] = response
|
||||
self.default = default
|
||||
self._insert_extras__()
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
{
|
||||
"_from": "swagger-ui-dist",
|
||||
"_id": "swagger-ui-dist@3.5.0",
|
||||
"_from": "swagger-ui-dist@3.6.1",
|
||||
"_id": "swagger-ui-dist@3.6.1",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY=",
|
||||
"_integrity": "sha1-uzQgV/h2COTs2DlGMDSJxjYicgY=",
|
||||
"_location": "/swagger-ui-dist",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "swagger-ui-dist",
|
||||
"raw": "swagger-ui-dist@3.6.1",
|
||||
"name": "swagger-ui-dist",
|
||||
"escapedName": "swagger-ui-dist",
|
||||
"rawSpec": "",
|
||||
"rawSpec": "3.6.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
"fetchSpec": "3.6.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
|
||||
"_shasum": "26ebf3311a9a60fe9d170612eed5282a65bc9226",
|
||||
"_spec": "swagger-ui-dist",
|
||||
"_where": "C:\\Projects\\drf_openapi",
|
||||
"_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.6.1.tgz",
|
||||
"_shasum": "bb342057f87608e4ecd83946303489c636227206",
|
||||
"_spec": "swagger-ui-dist@3.6.1",
|
||||
"_where": "C:\\Projects\\drf-swagger",
|
||||
"bugs": {
|
||||
"url": "https://github.com/swagger-api/swagger-ui/issues"
|
||||
},
|
||||
|
|
@ -68,5 +68,5 @@
|
|||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/swagger-api/swagger-ui.git"
|
||||
},
|
||||
"version": "3.5.0"
|
||||
"version": "3.6.1"
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -89,6 +89,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
|||
it will automatically be converted into a :class:`.Schema`
|
||||
|
||||
"""
|
||||
|
||||
def decorator(view_method):
|
||||
data = {
|
||||
'auto_schema': auto_schema,
|
||||
|
|
@ -165,6 +166,10 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
|||
def SwaggerType(**instance_kwargs):
|
||||
if swagger_object_type == openapi.Parameter:
|
||||
instance_kwargs['required'] = field.required
|
||||
if swagger_object_type != openapi.Items:
|
||||
default = getattr(field, 'default', serializers.empty)
|
||||
if default is not serializers.empty:
|
||||
instance_kwargs['default'] = default
|
||||
instance_kwargs.update(kwargs)
|
||||
return swagger_object_type(title=title, description=description, **instance_kwargs)
|
||||
|
||||
|
|
@ -249,7 +254,7 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
|||
elif isinstance(field, serializers.RegexField):
|
||||
return SwaggerType(type=openapi.TYPE_STRING, pattern=find_regex(field))
|
||||
elif isinstance(field, serializers.SlugField):
|
||||
return SwaggerType(type=openapi.TYPE_STRING, format=openapi.FORMAT_SLUG)
|
||||
return SwaggerType(type=openapi.TYPE_STRING, format=openapi.FORMAT_SLUG, pattern=find_regex(field))
|
||||
elif isinstance(field, serializers.URLField):
|
||||
return SwaggerType(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI)
|
||||
elif isinstance(field, serializers.IPAddressField):
|
||||
|
|
@ -272,11 +277,6 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
|||
if swagger_object_type != openapi.Parameter:
|
||||
raise SwaggerGenerationError("parameter of type file is supported only in formData Parameter")
|
||||
return SwaggerType(type=openapi.TYPE_FILE)
|
||||
elif isinstance(field, serializers.JSONField):
|
||||
return SwaggerType(
|
||||
type=openapi.TYPE_STRING,
|
||||
format=openapi.FORMAT_BINARY if field.binary else None
|
||||
)
|
||||
elif isinstance(field, serializers.DictField) and swagger_object_type == openapi.Schema:
|
||||
child_schema = serializer_field_to_swagger(field.child, ChildSwaggerType, definitions)
|
||||
return SwaggerType(
|
||||
|
|
@ -284,7 +284,7 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
|||
additional_properties=child_schema
|
||||
)
|
||||
|
||||
# TODO unhandled fields: TimeField DurationField HiddenField ModelField NullBooleanField?
|
||||
# TODO unhandled fields: TimeField DurationField HiddenField ModelField NullBooleanField? JSONField
|
||||
|
||||
# everything else gets string by default
|
||||
return SwaggerType(type=openapi.TYPE_STRING)
|
||||
|
|
@ -302,7 +302,7 @@ def find_regex(regex_field):
|
|||
if isinstance(validator, RegexValidator):
|
||||
if regex_validator is not None:
|
||||
# bail if multiple validators are found - no obvious way to choose
|
||||
return None
|
||||
return None # pragma: no cover
|
||||
regex_validator = validator
|
||||
|
||||
# regex_validator.regex should be a compiled re object...
|
||||
|
|
|
|||
|
|
@ -4,13 +4,24 @@ from articles.models import Article
|
|||
|
||||
|
||||
class ArticleSerializer(serializers.ModelSerializer):
|
||||
references = serializers.DictField(
|
||||
help_text="this is a really bad example",
|
||||
child=serializers.URLField(help_text="but i needed to test these 2 fields somehow"),
|
||||
)
|
||||
uuid = serializers.UUIDField(help_text="should articles have UUIDs?")
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = ('title', 'body', 'slug', 'date_created', 'date_modified')
|
||||
fields = ('title', 'body', 'slug', 'date_created', 'date_modified', 'references', 'uuid')
|
||||
read_only_fields = ('date_created', 'date_modified')
|
||||
lookup_field = 'slug'
|
||||
extra_kwargs = {'body': {'help_text': 'body serializer help_text'}}
|
||||
|
||||
|
||||
class ImageUploadSerializer(serializers.Serializer):
|
||||
what_am_i_doing = serializers.RegexField(regex=r"^69$", help_text="test")
|
||||
image_styles = serializers.ListSerializer(
|
||||
child=serializers.ChoiceField(choices=['wide', 'tall', 'thumb', 'social']),
|
||||
help_text="Parameter with Items"
|
||||
)
|
||||
upload = serializers.ImageField(help_text="image serializer help_text")
|
||||
|
|
|
|||
|
|
@ -28,9 +28,10 @@ class SnippetSerializer(serializers.Serializer):
|
|||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
||||
linenos = serializers.BooleanField(required=False)
|
||||
language = LanguageSerializer(help_text="Sample help text for language")
|
||||
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
|
||||
styles = serializers.MultipleChoiceField(choices=STYLE_CHOICES, default=['friendly'])
|
||||
lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False)
|
||||
example_projects = serializers.ListSerializer(child=ExampleProjectSerializer())
|
||||
difficulty_factor = serializers.FloatField(help_text="this is here just to test FloatField")
|
||||
|
||||
def create(self, validated_data):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -97,6 +97,12 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
)
|
||||
}
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'LOGIN_URL': '/admin/login',
|
||||
'LOGOUT_URL': '/admin/logout',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from django.conf.urls import url, include
|
||||
from django.contrib import admin
|
||||
from rest_framework import permissions
|
||||
from rest_framework.decorators import api_view
|
||||
|
||||
from drf_swagger import openapi
|
||||
from drf_swagger.views import get_schema_view
|
||||
|
|
@ -19,6 +20,12 @@ schema_view = get_schema_view(
|
|||
permission_classes=(permissions.AllowAny,),
|
||||
)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def plain_view(request):
|
||||
pass
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
|
|
@ -28,4 +35,5 @@ urlpatterns = [
|
|||
url(r'^snippets/', include('snippets.urls')),
|
||||
url(r'^articles/', include('articles.urls')),
|
||||
url(r'^users/', include('users.urls')),
|
||||
url(r'^plain/', plain_view),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ from rest_framework import serializers
|
|||
from snippets.models import Snippet
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class UserSerializerrr(serializers.ModelSerializer):
|
||||
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
|
||||
last_connected_ip = serializers.IPAddressField(help_text="i'm out of ideas", protocol='ipv4', read_only=True)
|
||||
last_connected_at = serializers.DateField(help_text="really?", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'snippets')
|
||||
fields = ('id', 'username', 'email', 'snippets', 'last_connected_ip', 'last_connected_at')
|
||||
|
|
|
|||
|
|
@ -7,16 +7,16 @@ from rest_framework.views import APIView
|
|||
|
||||
from drf_swagger import openapi
|
||||
from drf_swagger.utils import swagger_auto_schema, no_body
|
||||
from users.serializers import UserSerializer
|
||||
from users.serializers import UserSerializerrr
|
||||
|
||||
|
||||
class UserList(APIView):
|
||||
"""UserList cbv classdoc"""
|
||||
|
||||
@swagger_auto_schema(responses={200: UserSerializer(many=True)})
|
||||
@swagger_auto_schema(responses={200: UserSerializerrr(many=True)})
|
||||
def get(self, request):
|
||||
queryset = User.objects.all()
|
||||
serializer = UserSerializer(queryset, many=True)
|
||||
serializer = UserSerializerrr(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(operation_description="apiview post description override", request_body=openapi.Schema(
|
||||
|
|
@ -27,7 +27,7 @@ class UserList(APIView):
|
|||
},
|
||||
))
|
||||
def post(self, request):
|
||||
serializer = UserSerializer(request.data)
|
||||
serializer = UserSerializerrr(request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
|
@ -37,15 +37,15 @@ class UserList(APIView):
|
|||
pass
|
||||
|
||||
|
||||
@swagger_auto_schema(method='put', request_body=UserSerializer)
|
||||
@swagger_auto_schema(method='put', request_body=UserSerializerrr)
|
||||
@swagger_auto_schema(methods=['get'], manual_parameters=[
|
||||
openapi.Parameter('test', openapi.IN_QUERY, "test manual param", type=openapi.TYPE_BOOLEAN),
|
||||
], responses={
|
||||
200: openapi.Response('response description', UserSerializer),
|
||||
200: openapi.Response('response description', UserSerializerrr),
|
||||
})
|
||||
@api_view(['GET', 'PUT'])
|
||||
def user_detail(request, pk):
|
||||
"""user_detail fbv docstring"""
|
||||
user = get_object_or_404(User.objects, pk=pk)
|
||||
serializer = UserSerializer(user)
|
||||
serializer = UserSerializerrr(user)
|
||||
return Response(serializer.data)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import copy
|
||||
import json
|
||||
import os
|
||||
|
||||
|
|
@ -27,8 +28,13 @@ def codec_yaml():
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def swagger_dict():
|
||||
swagger = generator().get_schema(None, True)
|
||||
def swagger(generator):
|
||||
return generator.get_schema(None, True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def swagger_dict(generator):
|
||||
swagger = generator.get_schema(None, True)
|
||||
json_bytes = codec_json().encode(swagger)
|
||||
return json.loads(json_bytes.decode('utf-8'))
|
||||
|
||||
|
|
@ -46,16 +52,17 @@ def 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']
|
||||
def swagger_settings(settings):
|
||||
swagger_settings = copy.deepcopy(settings.SWAGGER_SETTINGS)
|
||||
settings.SWAGGER_SETTINGS = swagger_settings
|
||||
return swagger_settings
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def redoc_settings(settings):
|
||||
redoc_settings = copy.deepcopy(settings.REDOC_SETTINGS)
|
||||
settings.REDOC_SETTINGS = redoc_settings
|
||||
return redoc_settings
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
|
|
@ -141,17 +141,6 @@ paths:
|
|||
- application/json
|
||||
tags:
|
||||
- articles
|
||||
delete:
|
||||
operationId: articles_delete
|
||||
description: destroy method docstring
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
- articles
|
||||
patch:
|
||||
operationId: articles_partial_update
|
||||
description: partial_update description override
|
||||
|
|
@ -172,6 +161,17 @@ paths:
|
|||
- application/json
|
||||
tags:
|
||||
- articles
|
||||
delete:
|
||||
operationId: articles_delete
|
||||
description: destroy method docstring
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
- articles
|
||||
parameters:
|
||||
- name: slug
|
||||
in: path
|
||||
|
|
@ -197,6 +197,24 @@ paths:
|
|||
operationId: articles_image_create
|
||||
description: image method docstring
|
||||
parameters:
|
||||
- name: what_am_i_doing
|
||||
in: formData
|
||||
description: test
|
||||
required: true
|
||||
type: string
|
||||
pattern: ^69$
|
||||
- name: image_styles
|
||||
in: formData
|
||||
description: Parameter with Items
|
||||
required: true
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- wide
|
||||
- tall
|
||||
- thumb
|
||||
- social
|
||||
- name: upload
|
||||
in: formData
|
||||
description: image serializer help_text
|
||||
|
|
@ -216,6 +234,19 @@ paths:
|
|||
required: true
|
||||
type: string
|
||||
pattern: '[a-z0-9]+(?:-[a-z0-9]+)'
|
||||
/plain/:
|
||||
get:
|
||||
operationId: plain_list
|
||||
description: ''
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
- plain
|
||||
parameters: []
|
||||
/snippets/:
|
||||
get:
|
||||
operationId: snippets_list
|
||||
|
|
@ -283,17 +314,6 @@ paths:
|
|||
- application/json
|
||||
tags:
|
||||
- snippets
|
||||
delete:
|
||||
operationId: snippets_delete
|
||||
description: delete method docstring
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
- snippets
|
||||
patch:
|
||||
operationId: snippets_partial_update
|
||||
description: patch method docstring
|
||||
|
|
@ -312,6 +332,17 @@ paths:
|
|||
- application/json
|
||||
tags:
|
||||
- snippets
|
||||
delete:
|
||||
operationId: snippets_delete
|
||||
description: delete method docstring
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
- snippets
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
|
|
@ -329,7 +360,7 @@ paths:
|
|||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/User'
|
||||
$ref: '#/definitions/UserSerializerrr'
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
|
|
@ -381,7 +412,7 @@ paths:
|
|||
'200':
|
||||
description: response description
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
$ref: '#/definitions/UserSerializerrr'
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
|
|
@ -394,12 +425,12 @@ paths:
|
|||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
$ref: '#/definitions/UserSerializerrr'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
$ref: '#/definitions/UserSerializerrr'
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
|
|
@ -414,6 +445,8 @@ definitions:
|
|||
required:
|
||||
- title
|
||||
- body
|
||||
- references
|
||||
- uuid
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
|
|
@ -426,6 +459,7 @@ definitions:
|
|||
description: slug model help_text
|
||||
type: string
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
date_created:
|
||||
type: string
|
||||
format: date-time
|
||||
|
|
@ -434,6 +468,17 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
references:
|
||||
description: this is a really bad example
|
||||
type: object
|
||||
additionalProperties:
|
||||
description: but i needed to test these 2 fields somehow
|
||||
type: string
|
||||
format: uri
|
||||
uuid:
|
||||
description: should articles have UUIDs?
|
||||
type: string
|
||||
format: uuid
|
||||
Project:
|
||||
required:
|
||||
- project_name
|
||||
|
|
@ -451,6 +496,7 @@ definitions:
|
|||
- code
|
||||
- language
|
||||
- example_projects
|
||||
- difficulty_factor
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
|
|
@ -908,7 +954,10 @@ definitions:
|
|||
- yaml
|
||||
- yaml+jinja
|
||||
- zephir
|
||||
style:
|
||||
default: python
|
||||
styles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- abap
|
||||
|
|
@ -940,6 +989,8 @@ definitions:
|
|||
- vim
|
||||
- vs
|
||||
- xcode
|
||||
default:
|
||||
- friendly
|
||||
lines:
|
||||
type: array
|
||||
items:
|
||||
|
|
@ -948,7 +999,10 @@ definitions:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Project'
|
||||
User:
|
||||
difficulty_factor:
|
||||
description: this is here just to test FloatField
|
||||
type: number
|
||||
UserSerializerrr:
|
||||
required:
|
||||
- username
|
||||
- snippets
|
||||
|
|
@ -961,11 +1015,24 @@ definitions:
|
|||
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
|
||||
only.
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
snippets:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
uniqueItems: true
|
||||
last_connected_ip:
|
||||
description: i'm out of ideas
|
||||
type: string
|
||||
format: ipv4
|
||||
readOnly: true
|
||||
last_connected_at:
|
||||
description: really?
|
||||
type: string
|
||||
format: date
|
||||
readOnly: true
|
||||
securityDefinitions:
|
||||
basic:
|
||||
type: basic
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from drf_swagger import renderers
|
||||
|
||||
|
||||
def _check_swagger_setting(swagger, setting, expected):
|
||||
context = {}
|
||||
renderer = renderers.SwaggerUIRenderer()
|
||||
renderer.set_context(context, swagger)
|
||||
|
||||
swagger_settings = json.loads(context['swagger_settings'])
|
||||
assert swagger_settings[setting] == expected
|
||||
|
||||
|
||||
def _check_setting(swagger, setting, expected):
|
||||
context = {}
|
||||
renderer = renderers.SwaggerUIRenderer()
|
||||
renderer.set_context(context, swagger)
|
||||
assert context[setting] == expected
|
||||
|
||||
|
||||
def test_validator_url(swagger_settings, swagger):
|
||||
swagger_settings['VALIDATOR_URL'] = None
|
||||
_check_swagger_setting(swagger, 'validatorUrl', None)
|
||||
|
||||
swagger_settings['VALIDATOR_URL'] = 'not none'
|
||||
_check_swagger_setting(swagger, 'validatorUrl', 'not none')
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
swagger_settings['VALIDATOR_URL'] = ''
|
||||
_check_swagger_setting(swagger, 'validatorUrl', None)
|
||||
|
||||
|
||||
@pytest.mark.urls('urlconfs.login_test_urls')
|
||||
def test_login_logout(swagger_settings, swagger):
|
||||
swagger_settings['LOGIN_URL'] = 'login'
|
||||
_check_setting(swagger, 'LOGIN_URL', '/test/login')
|
||||
|
||||
swagger_settings['LOGOUT_URL'] = 'logout'
|
||||
_check_setting(swagger, 'LOGOUT_URL', '/test/logout')
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
swagger_settings['LOGIN_URL'] = None
|
||||
_check_setting(swagger, 'LOGIN_URL', None)
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
swagger_settings['LOGOUT_URL'] = None
|
||||
_check_setting(swagger, 'LOGOUT_URL', None)
|
||||
|
|
@ -12,7 +12,7 @@ def test_schema_generates_without_errors(generator):
|
|||
|
||||
|
||||
def test_schema_is_valid(generator, codec_yaml):
|
||||
swagger = generator.get_schema(None, True)
|
||||
swagger = generator.get_schema(request=None, public=False)
|
||||
codec_yaml.encode(swagger)
|
||||
|
||||
|
||||
|
|
@ -42,3 +42,17 @@ def test_yaml_codec_roundtrip(codec_yaml, generator, validate_schema):
|
|||
yaml_bytes = codec_yaml.encode(swagger)
|
||||
assert b'omap' not in yaml_bytes
|
||||
validate_schema(yaml.safe_load(yaml_bytes.decode('utf-8')))
|
||||
|
||||
|
||||
def test_basepath_only():
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title="Test generator", default_version="v1"),
|
||||
version="v2",
|
||||
url='/basepath/',
|
||||
)
|
||||
|
||||
swagger = generator.get_schema(None, public=True)
|
||||
assert 'host' not in swagger
|
||||
assert 'schemes' not in swagger
|
||||
assert swagger['basePath'] == '/' # base path is not implemented for now
|
||||
assert swagger['info']['version'] == 'v2'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
from ruamel import yaml
|
||||
|
||||
|
||||
|
|
@ -23,7 +24,13 @@ 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):
|
||||
def test_exception_middleware(client, swagger_settings):
|
||||
swagger_settings['SECURITY_DEFINITIONS'] = {
|
||||
'bad': {
|
||||
'bad_attribute': 'should not be accepted'
|
||||
}
|
||||
}
|
||||
|
||||
response = client.get('/swagger.json')
|
||||
assert response.status_code == 500
|
||||
assert 'errors' in json.loads(response.content.decode('utf-8'))
|
||||
|
|
@ -37,3 +44,10 @@ def test_swagger_ui(client, validate_schema):
|
|||
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)
|
||||
|
||||
|
||||
@pytest.mark.urls('urlconfs.non_public_urls')
|
||||
def test_non_public(client):
|
||||
response = client.get('/private/swagger.yaml')
|
||||
swagger = yaml.safe_load(response.content.decode('utf-8'))
|
||||
assert len(swagger['paths']) == 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
|
||||
def dummy(request):
|
||||
pass
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^test/login$', dummy, name='login'),
|
||||
url(r'^test/logout$', dummy, name='logout'),
|
||||
]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
from django.conf.urls import url
|
||||
from django.conf.urls import include
|
||||
from rest_framework import permissions
|
||||
|
||||
import testproj.urls
|
||||
from drf_swagger import openapi
|
||||
from drf_swagger.views import get_schema_view
|
||||
|
||||
view = get_schema_view(
|
||||
openapi.Info('bla', 'ble'),
|
||||
public=False,
|
||||
permission_classes=(permissions.AllowAny,)
|
||||
)
|
||||
view = view.without_ui(cache_timeout=None)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^', include(testproj.urls)),
|
||||
url(r'^private/swagger.yaml', view, name='schema-private'),
|
||||
]
|
||||
Loading…
Reference in New Issue