parent
9f14114520
commit
1f190744cd
|
|
@ -14,6 +14,7 @@ exclude_lines =
|
|||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
raise AssertionError
|
||||
raise ImproperlyConfigured
|
||||
raise TypeError
|
||||
raise NotImplementedError
|
||||
warnings.warn
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ Pull requests
|
|||
|
||||
You want to contribute some code? Great! Here are a few steps to get you started:
|
||||
|
||||
#. Fork the repository on GitHub
|
||||
#. Clone your fork and create a branch for the code you want to add
|
||||
#. Create a new virtualenv and install the package in development mode
|
||||
#. **Fork the repository on GitHub**
|
||||
#. **Clone your fork and create a branch for the code you want to add**
|
||||
#. **Create a new virtualenv and install the package in development mode**
|
||||
|
||||
.. code:: console
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
(venv) $ pip install -e .[validation]
|
||||
(venv) $ pip install -rrequirements/dev.txt -rrequirements/test.txt
|
||||
|
||||
#. Make your changes and check them against the test project
|
||||
#. **Make your changes and check them against the test project**
|
||||
|
||||
.. code:: console
|
||||
|
||||
|
|
@ -46,17 +46,24 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
(venv) $ python manage.py migrate
|
||||
(venv) $ cat createsuperuser.py | python manage.py shell
|
||||
(venv) $ python manage.py runserver
|
||||
(venv) $ curl localhost:8000/swagger.yaml
|
||||
(venv) $ firefox localhost:8000/swagger/
|
||||
|
||||
#. Update the tests if necessary
|
||||
#. **Update the tests if necessary**
|
||||
|
||||
You can find them in the ``tests`` directory.
|
||||
|
||||
If your change modifies the expected schema output, you should download the new generated ``swagger.yaml``, diff it
|
||||
against the old reference output in ``tests/reference.yaml``, and replace it after checking that no unexpected
|
||||
changes appeared.
|
||||
If your change modifies the expected schema output, you should regenerate the reference schema at
|
||||
``tests/reference.yaml``:
|
||||
|
||||
#. Run tests. The project is setup to use tox and pytest for testing
|
||||
.. code:: console
|
||||
|
||||
(venv) $ cd testproj
|
||||
(venv) $ python manage.py generate_swagger ../tests/reference.yaml --overwrite --user admin --url http://test.local:8002/
|
||||
|
||||
After checking the git diff to verify that no unexpected changes appeared, you should commit the new
|
||||
``reference.yaml`` together with your changes.
|
||||
|
||||
#. **Run tests. The project is setup to use tox and pytest for testing**
|
||||
|
||||
.. code:: console
|
||||
|
||||
|
|
@ -65,7 +72,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
# (optional) run tests for other python versions in separate environments
|
||||
(venv) $ tox
|
||||
|
||||
#. Update documentation
|
||||
#. **Update documentation**
|
||||
|
||||
If the change modifies behaviour or adds new features, you should update the documentation and ``README.rst``
|
||||
accordingly. Documentation is written in reStructuredText and built using Sphinx. You can find the sources in the
|
||||
|
|
@ -77,10 +84,11 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||
|
||||
(venv) $ tox -e docs
|
||||
|
||||
#. Push your branch and submit a pull request to the master branch on GitHub
|
||||
#. **Push your branch and submit a pull request to the master branch on GitHub**
|
||||
|
||||
Incomplete/Work In Progress pull requests are encouraged, because they allow you to get feedback and help more
|
||||
easily.
|
||||
|
||||
#. Your code must pass all the required travis jobs before it is merged. As of now, this includes running on
|
||||
Python 2.7, 3.4, 3.5 and 3.6, and building the docs succesfully.
|
||||
#. **Your code must pass all the required travis jobs before it is merged**
|
||||
|
||||
As of now, this consists of running on Python 2.7, 3.4, 3.5 and 3.6, and building the docs succesfully.
|
||||
|
|
|
|||
|
|
@ -201,6 +201,11 @@ The possible settings and their default values are as follows:
|
|||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
|
||||
# default api Info if none is otherwise given; should be an import string to an openapi.Info object
|
||||
'DEFAULT_INFO': None,
|
||||
# default API url if none is otherwise given
|
||||
'DEFAULT_API_URL': '',
|
||||
|
||||
'USE_SESSION_AUTH': True, # add Django Login and Django Logout buttons, CSRF token to swagger UI page
|
||||
'LOGIN_URL': getattr(django.conf.settings, 'LOGIN_URL', None), # URL for the login button
|
||||
'LOGOUT_URL': getattr(django.conf.settings, 'LOGOUT_URL', None), # URL for the logout button
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ Changelog
|
|||
#########
|
||||
|
||||
|
||||
*********
|
||||
**1.1.1**
|
||||
*********
|
||||
|
||||
- **ADDED:** :ref:`generate_swagger management command <management-command>`
|
||||
(:issue:`29`, :pr:`31`, thanks to :ghuser:`beaugunderson`)
|
||||
|
||||
*********
|
||||
**1.1.0**
|
||||
*********
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ The ``@swagger_auto_schema`` decorator
|
|||
You can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decorator on view functions to override
|
||||
some properties of the generated :class:`.Operation`. For example, in a ``ViewSet``,
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
|
|
@ -153,7 +153,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
|||
* for function based ``@api_view``\ s, because the same view can handle multiple methods, and thus represent multiple
|
||||
operations, you have to add the decorator multiple times if you want to override different operations:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
test_param = openapi.Parameter('test', openapi.IN_QUERY, description="test manual param", type=openapi.TYPE_BOOLEAN)
|
||||
user_response = openapi.Response('response description', UserSerializer)
|
||||
|
|
@ -169,7 +169,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
|||
* for class based ``APIView``, ``GenericAPIView`` and non-``ViewSet`` derivatives, you have to decorate the respective
|
||||
method of each operation:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
class UserList(APIView):
|
||||
@swagger_auto_schema(responses={200: UserSerializer(many=True)})
|
||||
|
|
@ -186,7 +186,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
|||
respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
|
||||
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
class ArticleViewSet(viewsets.ModelViewSet):
|
||||
# method or 'methods' can be skipped because the list_route only handles a single method (GET)
|
||||
|
|
@ -214,7 +214,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
|||
If you want to customize the generation of a method you are not implementing yourself, you can use
|
||||
``swagger_auto_schema`` in combination with Django's ``method_decorator``:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
@method_decorator(name='list', decorator=swagger_auto_schema(
|
||||
operation_description="description from swagger_auto_schema via method_decorator"
|
||||
|
|
@ -229,7 +229,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
|||
You can go even further and directly decorate the result of ``as_view``, in the same manner you would
|
||||
override an ``@api_view`` as described above:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
decorated_login_view = \
|
||||
swagger_auto_schema(
|
||||
|
|
@ -256,7 +256,7 @@ Serializer ``Meta`` nested class
|
|||
|
||||
You can define some per-serializer options by adding a ``Meta`` class to your serializer, e.g.:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
class WhateverSerializer(Serializer):
|
||||
...
|
||||
|
|
@ -288,7 +288,7 @@ class-level attribute named ``swagger_schema`` on the view class, or
|
|||
|
||||
For example, to generate all operation IDs as camel case, you could do:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
from inflection import camelize
|
||||
|
||||
|
|
@ -331,7 +331,7 @@ For customizing behavior related to specific field, serializer, filter or pagina
|
|||
A :class:`~.inspectors.FilterInspector` that adds a description to all ``DjangoFilterBackend`` parameters could be
|
||||
implemented like so:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
class DjangoFilterDescriptionInspector(CoreAPICompatInspector):
|
||||
def get_filter_parameters(self, filter_backend):
|
||||
|
|
@ -357,7 +357,7 @@ implemented like so:
|
|||
A second example, of a :class:`~.inspectors.FieldInspector` that removes the ``title`` attribute from all generated
|
||||
:class:`.Schema` objects:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
class NoSchemaTitleInspector(FieldInspector):
|
||||
def process_result(self, result, method_name, obj, **kwargs):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Serving the schema
|
||||
##################
|
||||
|
||||
|
||||
************************************************
|
||||
``get_schema_view`` and the ``SchemaView`` class
|
||||
************************************************
|
||||
|
|
@ -14,7 +15,7 @@ in the README for a usage example.
|
|||
|
||||
You can also subclass :class:`.SchemaView` by extending the return value of :func:`.get_schema_view`, e.g.:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
SchemaView = get_schema_view(info, ...)
|
||||
|
||||
|
|
@ -33,3 +34,27 @@ codec and the view.
|
|||
|
||||
You can use your custom renderer classes as kwargs to :meth:`.SchemaView.as_cached_view` or by subclassing
|
||||
:class:`.SchemaView`.
|
||||
|
||||
.. _management-command:
|
||||
|
||||
******************
|
||||
Management command
|
||||
******************
|
||||
|
||||
.. versionadded:: 1.1.1
|
||||
|
||||
If you only need a swagger spec file in YAML or JSON format, you can use the ``generate_swagger`` management command
|
||||
to get it without having to start the web server:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python manage.py generate_swagger swagger.json
|
||||
|
||||
See the command help for more advanced options:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python manage.py generate_swagger --help
|
||||
usage: manage.py generate_swagger [-h] [--version] [-v {0,1,2,3}]
|
||||
... more options ...
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Example:
|
|||
|
||||
**settings.py**
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'SECURITY_DEFINITIONS': {
|
||||
|
|
@ -91,6 +91,25 @@ Paginator inspectors given to :func:`@swagger_auto_schema <.swagger_auto_schema>
|
|||
:class:`'drf_yasg.inspectors.CoreAPICompatInspector' <.inspectors.CoreAPICompatInspector>`, |br| \
|
||||
``]``
|
||||
|
||||
Swagger document attributes
|
||||
===========================
|
||||
|
||||
DEFAULT_INFO
|
||||
------------
|
||||
|
||||
An import string to an :class:`.openapi.Info` object. This will be used when running the ``generate_swagger``
|
||||
management command, or if no ``info`` argument is passed to ``get_schema_view``.
|
||||
|
||||
**Default**: :python:`None`
|
||||
|
||||
DEFAULT_API_URL
|
||||
---------------
|
||||
|
||||
A string representing the default API URL. This will be used to populate the ``host``, ``schemes`` and ``basePath``
|
||||
attributes of the Swagger document if no API URL is otherwise provided.
|
||||
|
||||
**Default**: :python:`''`
|
||||
|
||||
Authorization
|
||||
=============
|
||||
|
||||
|
|
@ -124,7 +143,7 @@ See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#sec
|
|||
|
||||
**Default**:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
'basic': {
|
||||
'type': 'basic'
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ SWAGGER_DEFAULTS = {
|
|||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
|
||||
'DEFAULT_INFO': None,
|
||||
'DEFAULT_API_URL': '',
|
||||
|
||||
'USE_SESSION_AUTH': True,
|
||||
'SECURITY_DEFINITIONS': {
|
||||
'basic': {
|
||||
|
|
@ -53,6 +56,7 @@ IMPORT_STRINGS = [
|
|||
'DEFAULT_FIELD_INSPECTORS',
|
||||
'DEFAULT_FILTER_INSPECTORS',
|
||||
'DEFAULT_PAGINATOR_INSPECTORS',
|
||||
'DEFAULT_INFO',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ class OpenAPISchemaGenerator(object):
|
|||
"""
|
||||
endpoint_enumerator_class = EndpointEnumerator
|
||||
|
||||
def __init__(self, info, version, url=None, patterns=None, urlconf=None):
|
||||
def __init__(self, info, version='', url=swagger_settings.DEFAULT_API_URL, patterns=None, urlconf=None):
|
||||
"""
|
||||
|
||||
:param .Info info: information about the API
|
||||
:param str version: API version string, takes preedence over the version in `info`
|
||||
:param str url: API
|
||||
:param str version: API version string; can be omitted to use `info.default_version`
|
||||
:param str url: API url; can be empty to remove URL info from the result
|
||||
:param patterns: if given, only these patterns will be enumerated for inclusion in the API spec
|
||||
:param urlconf: if patterns is not given, use this urlconf to enumerate patterns;
|
||||
if not given, the default urlconf is used
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand
|
||||
from rest_framework.test import APIRequestFactory, force_authenticate
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from ... import openapi
|
||||
from ...app_settings import swagger_settings
|
||||
from ...codecs import OpenAPICodecJson, OpenAPICodecYaml
|
||||
from ...generators import OpenAPISchemaGenerator
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Write the Swagger schema to disk in JSON or YAML format.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'output_file', metavar='output-file',
|
||||
nargs='?',
|
||||
default='-',
|
||||
type=str,
|
||||
help='Output path for generated swagger document, or "-" for stdout.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-o', '--overwrite',
|
||||
default=False, action='store_true',
|
||||
help='Overwrite the output file if it already exists. '
|
||||
'Default behavior is to stop if the output file exists.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-f', '--format', dest='format',
|
||||
default='', choices=('json', 'yaml'),
|
||||
type=str,
|
||||
help='Output format. If not given, it is guessed from the output file extension and defaults to json.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-u', '--url', dest='api_url',
|
||||
default='',
|
||||
type=str,
|
||||
help='Base API URL - sets the host, scheme and basePath attributes of the generated document.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-m', '--mock-request', dest='mock',
|
||||
default=False, action='store_true',
|
||||
help='Use a mock request when generating the swagger schema. This is useful if your views or serializers'
|
||||
'depend on context from a request in order to function.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--user', dest='user',
|
||||
default='',
|
||||
help='Username of an existing user to use for mocked authentication. This option implies --mock-request.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--private',
|
||||
default=False, action="store_true",
|
||||
help='Hides endpoints not accesible to the target user. If --user is not given, only shows endpoints that '
|
||||
'are accesible to unauthenticated users.\n'
|
||||
'This has the same effect as passing public=False to get_schema_view() or '
|
||||
'OpenAPISchemaGenerator.get_schema().\n'
|
||||
'This option implies --mock-request.'
|
||||
)
|
||||
|
||||
def write_schema(self, schema, stream, format):
|
||||
if format == 'json':
|
||||
codec = OpenAPICodecJson(validators=[])
|
||||
swagger_json = codec.encode(schema)
|
||||
swagger_json = json.loads(swagger_json.decode('utf-8'), object_pairs_hook=OrderedDict)
|
||||
pretty_json = json.dumps(swagger_json, indent=4, ensure_ascii=True)
|
||||
stream.write(pretty_json)
|
||||
elif format == 'yaml':
|
||||
codec = OpenAPICodecYaml(validators=[])
|
||||
swagger_yaml = codec.encode(schema).decode('utf-8')
|
||||
# YAML is already pretty!
|
||||
stream.write(swagger_yaml)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("unknown format %s" % format)
|
||||
|
||||
def get_mock_request(self, url, format, user=None):
|
||||
factory = APIRequestFactory()
|
||||
|
||||
request = factory.get(url + '/swagger.' + format)
|
||||
if user is not None:
|
||||
force_authenticate(request, user=user)
|
||||
request = APIView().initialize_request(request)
|
||||
return request
|
||||
|
||||
def handle(self, output_file, overwrite, format, api_url, mock, user, private, *args, **options):
|
||||
# disable logs of WARNING and below
|
||||
logging.disable(logging.WARNING)
|
||||
|
||||
info = getattr(swagger_settings, 'DEFAULT_INFO', None)
|
||||
if not isinstance(info, openapi.Info):
|
||||
raise ImproperlyConfigured(
|
||||
'settings.SWAGGER_SETTINGS["DEFAULT_INFO"] should be an '
|
||||
'import string pointing to an openapi.Info object'
|
||||
)
|
||||
|
||||
if not format:
|
||||
if os.path.splitext(output_file)[1] in ('.yml', '.yaml'):
|
||||
format = 'yaml'
|
||||
format = format or 'json'
|
||||
|
||||
api_url = api_url or swagger_settings.DEFAULT_API_URL
|
||||
|
||||
user = User.objects.get(username=user) if user else None
|
||||
mock = mock or private or (user is not None)
|
||||
if mock and not api_url:
|
||||
raise ImproperlyConfigured(
|
||||
'--mock-request requires an API url; either provide '
|
||||
'the --url argument or set the DEFAULT_API_URL setting'
|
||||
)
|
||||
|
||||
request = self.get_mock_request(api_url, format, user) if mock else None
|
||||
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=info,
|
||||
url=api_url
|
||||
)
|
||||
schema = generator.get_schema(request=request, public=not private)
|
||||
|
||||
if output_file == '-':
|
||||
self.write_schema(schema, self.stdout, format)
|
||||
else:
|
||||
flags = os.O_CREAT | os.O_WRONLY
|
||||
flags = flags | (os.O_TRUNC if overwrite else os.O_EXCL)
|
||||
with os.fdopen(os.open(output_file, flags), "w") as stream:
|
||||
self.write_schema(schema, stream, format)
|
||||
|
|
@ -10,6 +10,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from drf_yasg.app_settings import swagger_settings
|
||||
from .generators import OpenAPISchemaGenerator
|
||||
from .renderers import (
|
||||
SwaggerJSONRenderer, SwaggerYAMLRenderer, SwaggerUIRenderer, ReDocRenderer, OpenAPIRenderer,
|
||||
|
|
@ -46,14 +47,14 @@ def deferred_never_cache(view_func):
|
|||
return _wrapped_view_func
|
||||
|
||||
|
||||
def get_schema_view(info, url=None, patterns=None, urlconf=None, public=False, validators=None,
|
||||
def get_schema_view(info=None, url=None, patterns=None, urlconf=None, public=False, validators=None,
|
||||
generator_class=OpenAPISchemaGenerator,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||
"""
|
||||
Create a SchemaView class with default renderers and generators.
|
||||
|
||||
:param .Info info: Required. Swagger API Info object
|
||||
:param .Info info: Swagger API Info object; if omitted, defaults to `DEFAULT_INFO`
|
||||
:param str url: API base url; if left blank will be deduced from the location the view is served at
|
||||
:param patterns: passed to SchemaGenerator
|
||||
:param urlconf: passed to SchemaGenerator
|
||||
|
|
@ -69,6 +70,7 @@ def get_schema_view(info, url=None, patterns=None, urlconf=None, public=False, v
|
|||
_generator_class = generator_class
|
||||
_auth_classes = authentication_classes
|
||||
_perm_classes = permission_classes
|
||||
info = info or swagger_settings.DEFAULT_INFO
|
||||
validators = validators or []
|
||||
_spec_renderers = tuple(renderer.with_validators(validators) for renderer in SPEC_RENDERERS)
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ SWAGGER_SETTINGS = {
|
|||
'LOGIN_URL': '/admin/login',
|
||||
'LOGOUT_URL': '/admin/logout',
|
||||
'VALIDATOR_URL': 'http://localhost:8189',
|
||||
|
||||
'DEFAULT_INFO': 'testproj.urls.swagger_info'
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
|
|
|
|||
|
|
@ -6,15 +6,16 @@ from rest_framework.decorators import api_view
|
|||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
|
||||
SchemaView = get_schema_view(
|
||||
openapi.Info(
|
||||
swagger_info = openapi.Info(
|
||||
title="Snippets API",
|
||||
default_version='v1',
|
||||
description="Test description",
|
||||
terms_of_service="https://www.google.com/policies/terms/",
|
||||
contact=openapi.Contact(email="contact@snippets.local"),
|
||||
license=openapi.License(name="BSD License"),
|
||||
),
|
||||
)
|
||||
|
||||
SchemaView = get_schema_view(
|
||||
validators=['ssv', 'flex'],
|
||||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import os
|
|||
from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
from datadiff.tools import assert_equal
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from drf_yasg import openapi, codecs
|
||||
from drf_yasg.codecs import yaml_sane_load
|
||||
from drf_yasg.codecs import yaml_sane_load, yaml_sane_dump
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
|
||||
|
||||
|
|
@ -63,6 +64,22 @@ def validate_schema(db):
|
|||
return validate_schema
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def compare_schemas():
|
||||
def compare_schemas(schema1, schema2):
|
||||
schema1 = OrderedDict(schema1)
|
||||
schema2 = OrderedDict(schema2)
|
||||
ignore = ['info', 'host', 'schemes', 'basePath', 'securityDefinitions']
|
||||
for attr in ignore:
|
||||
schema1.pop(attr, None)
|
||||
schema2.pop(attr, None)
|
||||
|
||||
# print diff between YAML strings because it's prettier
|
||||
assert_equal(yaml_sane_dump(schema1, binary=False), yaml_sane_dump(schema2, binary=False))
|
||||
|
||||
return compare_schemas
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def swagger_settings(settings):
|
||||
swagger_settings = copy.deepcopy(settings.SWAGGER_SETTINGS)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import json
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from six import StringIO
|
||||
|
||||
from drf_yasg.codecs import yaml_sane_load
|
||||
|
||||
|
||||
def call_generate_swagger(output_file='-', overwrite=False, format='', api_url='',
|
||||
mock=False, user='', private=False, **kwargs):
|
||||
out = StringIO()
|
||||
call_command(
|
||||
'generate_swagger', stdout=out,
|
||||
output_file=output_file, overwrite=overwrite, format=format,
|
||||
api_url=api_url, mock=mock, user=user, private=private,
|
||||
**kwargs
|
||||
)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def test_reference_schema(db, reference_schema):
|
||||
User.objects.create_superuser('admin', 'admin@admin.admin', 'blabla')
|
||||
|
||||
output = call_generate_swagger(format='yaml', api_url='http://test.local:8002/', user='admin')
|
||||
output_schema = yaml_sane_load(output)
|
||||
assert output_schema == reference_schema
|
||||
|
||||
|
||||
def test_non_public(db):
|
||||
output = call_generate_swagger(format='yaml', api_url='http://test.local:8002/', private=True)
|
||||
output_schema = yaml_sane_load(output)
|
||||
assert len(output_schema['paths']) == 0
|
||||
|
||||
|
||||
def test_no_mock(db):
|
||||
output = call_generate_swagger()
|
||||
output_schema = json.loads(output, object_pairs_hook=OrderedDict)
|
||||
assert len(output_schema['paths']) > 0
|
||||
|
||||
|
||||
def silentremove(filename):
|
||||
try:
|
||||
os.remove(filename)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_file_output(db):
|
||||
prefix = os.path.join(tempfile.gettempdir(), tempfile.gettempprefix())
|
||||
name = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(8))
|
||||
yaml_file = prefix + name + '.yaml'
|
||||
json_file = prefix + name + '.json'
|
||||
other_file = prefix + name + '.txt'
|
||||
|
||||
try:
|
||||
# when called with output file nothing should be written to stdout
|
||||
assert call_generate_swagger(output_file=yaml_file) == ''
|
||||
assert call_generate_swagger(output_file=json_file) == ''
|
||||
assert call_generate_swagger(output_file=other_file) == ''
|
||||
|
||||
with pytest.raises(OSError):
|
||||
# a second call should fail because file exists
|
||||
call_generate_swagger(output_file=yaml_file)
|
||||
|
||||
# a second call with overwrite should still succeed
|
||||
assert call_generate_swagger(output_file=json_file, overwrite=True) == ''
|
||||
|
||||
with open(yaml_file) as f:
|
||||
content = f.read()
|
||||
# YAML is a superset of JSON - that means we have to check that
|
||||
# the file is really YAML and not just JSON parsed by the YAML parser
|
||||
with pytest.raises(ValueError):
|
||||
json.loads(content)
|
||||
output_yaml = yaml_sane_load(content)
|
||||
with open(json_file) as f:
|
||||
output_json = json.load(f, object_pairs_hook=OrderedDict)
|
||||
with open(other_file) as f:
|
||||
output_other = json.load(f, object_pairs_hook=OrderedDict)
|
||||
|
||||
assert output_yaml == output_json == output_other
|
||||
finally:
|
||||
silentremove(yaml_file)
|
||||
silentremove(json_file)
|
||||
silentremove(other_file)
|
||||
|
|
@ -1,21 +1,8 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from datadiff.tools import assert_equal
|
||||
|
||||
from drf_yasg.codecs import yaml_sane_dump
|
||||
from drf_yasg.inspectors import FieldInspector, SerializerInspector, PaginatorInspector, FilterInspector
|
||||
|
||||
|
||||
def test_reference_schema(swagger_dict, reference_schema):
|
||||
swagger_dict = OrderedDict(swagger_dict)
|
||||
reference_schema = OrderedDict(reference_schema)
|
||||
ignore = ['info', 'host', 'schemes', 'basePath', 'securityDefinitions']
|
||||
for attr in ignore:
|
||||
swagger_dict.pop(attr, None)
|
||||
reference_schema.pop(attr, None)
|
||||
|
||||
# print diff between YAML strings because it's prettier
|
||||
assert_equal(yaml_sane_dump(swagger_dict, binary=False), yaml_sane_dump(reference_schema, binary=False))
|
||||
def test_reference_schema(swagger_dict, reference_schema, compare_schemas):
|
||||
compare_schemas(swagger_dict, reference_schema)
|
||||
|
||||
|
||||
class NoOpFieldInspector(FieldInspector):
|
||||
|
|
@ -34,7 +21,7 @@ class NoOpPaginatorInspector(PaginatorInspector):
|
|||
pass
|
||||
|
||||
|
||||
def test_noop_inspectors(swagger_settings, swagger_dict, reference_schema):
|
||||
def test_noop_inspectors(swagger_settings, swagger_dict, reference_schema, compare_schemas):
|
||||
from drf_yasg import app_settings
|
||||
|
||||
def set_inspectors(inspectors, setting_name):
|
||||
|
|
@ -43,4 +30,4 @@ def test_noop_inspectors(swagger_settings, swagger_dict, reference_schema):
|
|||
set_inspectors([NoOpFieldInspector, NoOpSerializerInspector], 'DEFAULT_FIELD_INSPECTORS')
|
||||
set_inspectors([NoOpFilterInspector], 'DEFAULT_FILTER_INSPECTORS')
|
||||
set_inspectors([NoOpPaginatorInspector], 'DEFAULT_PAGINATOR_INSPECTORS')
|
||||
test_reference_schema(swagger_dict, reference_schema)
|
||||
compare_schemas(swagger_dict, reference_schema)
|
||||
|
|
|
|||
Loading…
Reference in New Issue