Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eba228a114 | |||
| ee4789776a | |||
| 22853b87c7 | |||
| d39764d383 | |||
| 60e266fe99 | |||
| 1f190744cd | |||
| 9f14114520 | |||
| c85acee405 |
@@ -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
|
||||
|
||||
+3
-2
@@ -50,7 +50,7 @@ jobs:
|
||||
- stage: publish
|
||||
python: '3.6'
|
||||
script: skip
|
||||
env:
|
||||
env:
|
||||
deploy: &pypi
|
||||
provider: pypi
|
||||
user: cvijdea
|
||||
@@ -67,4 +67,5 @@ jobs:
|
||||
|
||||
stages:
|
||||
- test
|
||||
- publish
|
||||
- name: publish
|
||||
if: tag IS present
|
||||
|
||||
+26
-15
@@ -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,26 +46,36 @@ 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.
|
||||
|
||||
#. Run tests. The project is setup to use tox and pytest for testing
|
||||
If your change modifies the expected schema output, you should regenerate the reference schema at
|
||||
``tests/reference.yaml``:
|
||||
|
||||
.. 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
|
||||
|
||||
# (optional) sort imports with isort and check flake8 linting
|
||||
(venv) $ isort --apply
|
||||
(venv) $ flake8 src/drf_yasg testproj tests setup.py
|
||||
# run tests in the current environment, faster than tox
|
||||
(venv) $ pytest --cov
|
||||
# (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 +87,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.
|
||||
|
||||
+54
-49
@@ -180,62 +180,67 @@ The possible settings and their default values are as follows:
|
||||
|
||||
.. code:: python
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
# default inspector classes, see advanced documentation
|
||||
'DEFAULT_AUTO_SCHEMA_CLASS': 'drf_yasg.inspectors.SwaggerAutoSchema',
|
||||
'DEFAULT_FIELD_INSPECTORS': [
|
||||
'drf_yasg.inspectors.CamelCaseJSONFilter',
|
||||
'drf_yasg.inspectors.ReferencingSerializerInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||
'drf_yasg.inspectors.FileFieldInspector',
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||
],
|
||||
'DEFAULT_FILTER_INSPECTORS': [
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
'DEFAULT_PAGINATOR_INSPECTORS': [
|
||||
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
SWAGGER_SETTINGS = {
|
||||
# default inspector classes, see advanced documentation
|
||||
'DEFAULT_AUTO_SCHEMA_CLASS': 'drf_yasg.inspectors.SwaggerAutoSchema',
|
||||
'DEFAULT_FIELD_INSPECTORS': [
|
||||
'drf_yasg.inspectors.CamelCaseJSONFilter',
|
||||
'drf_yasg.inspectors.ReferencingSerializerInspector',
|
||||
'drf_yasg.inspectors.RelatedFieldInspector',
|
||||
'drf_yasg.inspectors.ChoiceFieldInspector',
|
||||
'drf_yasg.inspectors.FileFieldInspector',
|
||||
'drf_yasg.inspectors.DictFieldInspector',
|
||||
'drf_yasg.inspectors.SimpleFieldInspector',
|
||||
'drf_yasg.inspectors.StringDefaultFieldInspector',
|
||||
],
|
||||
'DEFAULT_FILTER_INSPECTORS': [
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
'DEFAULT_PAGINATOR_INSPECTORS': [
|
||||
'drf_yasg.inspectors.DjangoRestResponsePagination',
|
||||
'drf_yasg.inspectors.CoreAPICompatInspector',
|
||||
],
|
||||
|
||||
'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
|
||||
# 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': '',
|
||||
|
||||
# Swagger security definitions to include in the schema;
|
||||
# see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-definitions-object
|
||||
'SECURITY_DEFINITIONS': {
|
||||
'basic': {
|
||||
'type': 'basic'
|
||||
}
|
||||
},
|
||||
'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
|
||||
|
||||
# url to an external Swagger validation service; defaults to 'http://online.swagger.io/validator/'
|
||||
# set to None to disable the schema validation badge in the UI
|
||||
'VALIDATOR_URL': '',
|
||||
# Swagger security definitions to include in the schema;
|
||||
# see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-definitions-object
|
||||
'SECURITY_DEFINITIONS': {
|
||||
'basic': {
|
||||
'type': 'basic'
|
||||
}
|
||||
},
|
||||
|
||||
# swagger-ui configuration settings, see https://github.com/swagger-api/swagger-ui/blob/112bca906553a937ac67adc2e500bdeed96d067b/docs/usage/configuration.md#parameters
|
||||
'OPERATIONS_SORTER': None,
|
||||
'TAGS_SORTER': None,
|
||||
'DOC_EXPANSION': 'list',
|
||||
'DEEP_LINKING': False,
|
||||
'SHOW_EXTENSIONS': True,
|
||||
'DEFAULT_MODEL_RENDERING': 'model',
|
||||
'DEFAULT_MODEL_DEPTH': 3,
|
||||
}
|
||||
# url to an external Swagger validation service; defaults to 'http://online.swagger.io/validator/'
|
||||
# set to None to disable the schema validation badge in the UI
|
||||
'VALIDATOR_URL': '',
|
||||
|
||||
# swagger-ui configuration settings, see https://github.com/swagger-api/swagger-ui/blob/112bca906553a937ac67adc2e500bdeed96d067b/docs/usage/configuration.md#parameters
|
||||
'OPERATIONS_SORTER': None,
|
||||
'TAGS_SORTER': None,
|
||||
'DOC_EXPANSION': 'list',
|
||||
'DEEP_LINKING': False,
|
||||
'SHOW_EXTENSIONS': True,
|
||||
'DEFAULT_MODEL_RENDERING': 'model',
|
||||
'DEFAULT_MODEL_DEPTH': 3,
|
||||
}
|
||||
|
||||
.. code:: python
|
||||
|
||||
REDOC_SETTINGS = {
|
||||
# ReDoc UI configuration settings, see https://github.com/Rebilly/ReDoc#redoc-tag-attributes
|
||||
'LAZY_RENDERING': True,
|
||||
'HIDE_HOSTNAME': False,
|
||||
'EXPAND_RESPONSES': 'all',
|
||||
'PATH_IN_MIDDLE': False,
|
||||
}
|
||||
REDOC_SETTINGS = {
|
||||
# ReDoc UI configuration settings, see https://github.com/Rebilly/ReDoc#redoc-tag-attributes
|
||||
'LAZY_RENDERING': True,
|
||||
'HIDE_HOSTNAME': False,
|
||||
'EXPAND_RESPONSES': 'all',
|
||||
'PATH_IN_MIDDLE': False,
|
||||
}
|
||||
|
||||
3. Caching
|
||||
==========
|
||||
|
||||
@@ -3,6 +3,14 @@ Changelog
|
||||
#########
|
||||
|
||||
|
||||
*********
|
||||
**1.1.1**
|
||||
*********
|
||||
|
||||
- **ADDED:** :ref:`generate_swagger management command <management-command>`
|
||||
(:issue:`29`, :pr:`31`, thanks to :ghuser:`beaugunderson`)
|
||||
- **FIXED:** fixed improper generation of ``\Z`` regex tokens - will now be repalced by ``$``
|
||||
|
||||
*********
|
||||
**1.1.0**
|
||||
*********
|
||||
|
||||
+10
-10
@@ -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):
|
||||
|
||||
+26
-1
@@ -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 ...
|
||||
|
||||
|
||||
+21
-2
@@ -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'
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# requirements for local development
|
||||
tox>=2.9.1
|
||||
tox-battery>=0.5
|
||||
detox>=0.11
|
||||
|
||||
isort>=4.2
|
||||
flake8>=3.5.0
|
||||
flake8-isort>=2.3
|
||||
|
||||
# do not unpin this (see setup.py)
|
||||
setuptools_scm==1.15.6
|
||||
|
||||
@@ -4,3 +4,4 @@ Pillow==4.3.0
|
||||
readme_renderer==17.2
|
||||
|
||||
Django==2.0
|
||||
djangorestframework_camel_case>=0.2.0
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import distutils.core
|
||||
import io
|
||||
import os
|
||||
|
||||
import sys
|
||||
from setuptools import setup, find_packages
|
||||
import distutils.core
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
requirements_setup = ['setuptools_scm==1.15.6']
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# coding=utf-8
|
||||
from pkg_resources import get_distribution, DistributionNotFound
|
||||
from pkg_resources import DistributionNotFound, get_distribution
|
||||
|
||||
__author__ = """Cristi V."""
|
||||
__email__ = 'cristi@cvjd.me'
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from future.utils import raise_from
|
||||
|
||||
import copy
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from coreapi.compat import force_bytes
|
||||
from future.utils import raise_from
|
||||
from ruamel import yaml
|
||||
|
||||
from . import openapi
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import re
|
||||
from collections import defaultdict, OrderedDict
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import uritemplate
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import versioning
|
||||
from rest_framework.schemas.generators import SchemaGenerator, EndpointEnumerator as _EndpointEnumerator
|
||||
from rest_framework.schemas.generators import EndpointEnumerator as _EndpointEnumerator
|
||||
from rest_framework.schemas.generators import SchemaGenerator
|
||||
from rest_framework.schemas.inspectors import get_pk_description
|
||||
|
||||
from . import openapi
|
||||
from .app_settings import swagger_settings
|
||||
from .inspectors.field import get_queryset_field, get_basic_type_info
|
||||
from .inspectors.field import get_basic_type_info, get_queryset_field
|
||||
from .openapi import ReferenceResolver
|
||||
|
||||
PATH_PARAMETER_RE = re.compile(r'{(?P<parameter>\w+)}')
|
||||
@@ -59,12 +60,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
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
from ..app_settings import swagger_settings
|
||||
from .base import (
|
||||
BaseInspector, ViewInspector, FilterInspector, PaginatorInspector,
|
||||
FieldInspector, SerializerInspector, NotHandled
|
||||
BaseInspector, FieldInspector, FilterInspector, NotHandled, PaginatorInspector, SerializerInspector, ViewInspector
|
||||
)
|
||||
from .field import (
|
||||
InlineSerializerInspector, ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector,
|
||||
FileFieldInspector, ChoiceFieldInspector, DictFieldInspector, StringDefaultFieldInspector,
|
||||
CamelCaseJSONFilter
|
||||
)
|
||||
from .query import (
|
||||
CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
CamelCaseJSONFilter, ChoiceFieldInspector, DictFieldInspector, FileFieldInspector, InlineSerializerInspector,
|
||||
ReferencingSerializerInspector, RelatedFieldInspector, SimpleFieldInspector, StringDefaultFieldInspector
|
||||
)
|
||||
from .query import CoreAPICompatInspector, DjangoRestResponsePagination
|
||||
from .view import SwaggerAutoSchema
|
||||
from ..app_settings import swagger_settings
|
||||
|
||||
# these settings must be accesed only after definig/importing all the classes in this module to avoid ImportErrors
|
||||
ViewInspector.field_inspectors = swagger_settings.DEFAULT_FIELD_INSPECTORS
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import serializers
|
||||
from rest_framework.utils import json, encoders
|
||||
from rest_framework.utils import encoders, json
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from .. import openapi
|
||||
|
||||
@@ -6,10 +6,10 @@ from django.db import models
|
||||
from rest_framework import serializers
|
||||
from rest_framework.settings import api_settings as rest_framework_settings
|
||||
|
||||
from .base import NotHandled, SerializerInspector, FieldInspector
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import filter_none
|
||||
from .base import FieldInspector, NotHandled, SerializerInspector
|
||||
|
||||
|
||||
class InlineSerializerInspector(SerializerInspector):
|
||||
@@ -211,7 +211,14 @@ def find_regex(regex_field):
|
||||
regex_validator = validator
|
||||
|
||||
# regex_validator.regex should be a compiled re object...
|
||||
return getattr(getattr(regex_validator, 'regex', None), 'pattern', None)
|
||||
pattern = getattr(getattr(regex_validator, 'regex', None), 'pattern', None)
|
||||
if pattern:
|
||||
# attempt some basic cleanup to remove regex constructs not supported by JavaScript
|
||||
# -- swagger uses javascript-style regexes - see https://github.com/swagger-api/swagger-editor/issues/1601
|
||||
if pattern.endswith('\\Z') or pattern.endswith('\\z'):
|
||||
pattern = pattern[:-2] + '$'
|
||||
|
||||
return pattern
|
||||
|
||||
|
||||
numeric_fields = (serializers.IntegerField, serializers.FloatField, serializers.DecimalField)
|
||||
@@ -423,6 +430,7 @@ try:
|
||||
from djangorestframework_camel_case.render import camelize
|
||||
except ImportError: # pragma: no cover
|
||||
class CamelCaseJSONFilter(FieldInspector):
|
||||
"""Converts property names to camelCase if ``djangorestframework_camel_case`` is used."""
|
||||
pass
|
||||
else:
|
||||
def camelize_string(s):
|
||||
@@ -444,6 +452,8 @@ else:
|
||||
return schema_or_ref
|
||||
|
||||
class CamelCaseJSONFilter(FieldInspector):
|
||||
"""Converts property names to camelCase if ``CamelCaseJSONParser`` or ``CamelCaseJSONRenderer`` are used."""
|
||||
|
||||
def is_camel_case(self):
|
||||
return any(issubclass(parser, CamelCaseJSONParser) for parser in self.view.parser_classes) \
|
||||
or any(issubclass(renderer, CamelCaseJSONRenderer) for renderer in self.view.renderer_classes)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import coreschema
|
||||
from rest_framework.pagination import CursorPagination, PageNumberPagination, LimitOffsetPagination
|
||||
from rest_framework.pagination import CursorPagination, LimitOffsetPagination, PageNumberPagination
|
||||
|
||||
from .base import PaginatorInspector, FilterInspector
|
||||
from .. import openapi
|
||||
from .base import FilterInspector, PaginatorInspector
|
||||
|
||||
|
||||
class CoreAPICompatInspector(PaginatorInspector, FilterInspector):
|
||||
|
||||
@@ -4,10 +4,10 @@ from rest_framework.request import is_form_media_type
|
||||
from rest_framework.schemas import AutoSchema
|
||||
from rest_framework.status import is_success
|
||||
|
||||
from .base import ViewInspector
|
||||
from .. import openapi
|
||||
from ..errors import SwaggerGenerationError
|
||||
from ..utils import force_serializer_instance, no_body, is_list_view, param_list_to_odict, guess_response_status
|
||||
from ..utils import force_serializer_instance, guess_response_status, is_list_view, no_body, param_list_to_odict
|
||||
from .base import ViewInspector
|
||||
|
||||
|
||||
class SwaggerAutoSchema(ViewInspector):
|
||||
@@ -162,9 +162,7 @@ class SwaggerAutoSchema(ViewInspector):
|
||||
|
||||
default_status = guess_response_status(method)
|
||||
default_schema = ''
|
||||
if method == 'post':
|
||||
default_schema = self.get_request_serializer() or self.get_view_serializer()
|
||||
elif method in ('get', 'put', 'patch'):
|
||||
if method in ('get', 'post', 'put', 'patch'):
|
||||
default_schema = self.get_request_serializer() or self.get_view_serializer()
|
||||
|
||||
default_schema = default_schema or ''
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
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:
|
||||
# normally this would be easily done with open(mode='x'/'w'),
|
||||
# but python 2 is a pain in the ass as usual
|
||||
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)
|
||||
@@ -2,8 +2,8 @@ from django.shortcuts import render, resolve_url
|
||||
from rest_framework.renderers import BaseRenderer
|
||||
from rest_framework.utils import json
|
||||
|
||||
from .app_settings import swagger_settings, redoc_settings
|
||||
from .codecs import OpenAPICodecJson, VALIDATORS, OpenAPICodecYaml
|
||||
from .app_settings import redoc_settings, swagger_settings
|
||||
from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml
|
||||
|
||||
|
||||
class _SpecRenderer(BaseRenderer):
|
||||
|
||||
@@ -2,8 +2,8 @@ import inspect
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
from rest_framework import status, serializers
|
||||
from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -10,10 +10,9 @@ from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .app_settings import swagger_settings
|
||||
from .generators import OpenAPISchemaGenerator
|
||||
from .renderers import (
|
||||
SwaggerJSONRenderer, SwaggerYAMLRenderer, SwaggerUIRenderer, ReDocRenderer, OpenAPIRenderer,
|
||||
)
|
||||
from .renderers import OpenAPIRenderer, ReDocRenderer, SwaggerJSONRenderer, SwaggerUIRenderer, SwaggerYAMLRenderer
|
||||
|
||||
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
|
||||
UI_RENDERERS = {
|
||||
@@ -46,14 +45,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 +68,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)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from articles.models import Article
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class ArticleSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -13,7 +13,7 @@ from articles import serializers
|
||||
from articles.models import Article
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.app_settings import swagger_settings
|
||||
from drf_yasg.inspectors import SwaggerAutoSchema, FieldInspector, CoreAPICompatInspector, NotHandled
|
||||
from drf_yasg.inspectors import CoreAPICompatInspector, FieldInspector, NotHandled, SwaggerAutoSchema
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
|
||||
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
|
||||
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet
|
||||
|
||||
|
||||
class LanguageSerializer(serializers.Serializer):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from django.conf.urls import url, include
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from rest_framework import permissions
|
||||
from rest_framework.decorators import api_view
|
||||
@@ -6,15 +6,16 @@ from rest_framework.decorators import api_view
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
|
||||
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(
|
||||
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"),
|
||||
),
|
||||
validators=['ssv', 'flex'],
|
||||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
|
||||
@@ -6,8 +6,8 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema, no_body
|
||||
from users.serializers import UserSerializerrr, UserListQuerySerializer
|
||||
from drf_yasg.utils import no_body, swagger_auto_schema
|
||||
from users.serializers import UserListQuerySerializer, UserSerializerrr
|
||||
|
||||
|
||||
class UserList(APIView):
|
||||
|
||||
+21
-4
@@ -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 import codecs, openapi
|
||||
from drf_yasg.codecs import yaml_sane_dump, yaml_sane_load
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
|
||||
|
||||
@@ -46,8 +47,8 @@ def swagger(mock_schema_request):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def swagger_dict(swagger):
|
||||
json_bytes = codec_json().encode(swagger)
|
||||
def swagger_dict(swagger, codec_json):
|
||||
json_bytes = codec_json.encode(swagger)
|
||||
return json.loads(json_bytes.decode('utf-8'), object_pairs_hook=OrderedDict)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1108,7 +1108,7 @@ definitions:
|
||||
items:
|
||||
type: string
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+\Z
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
readOnly: true
|
||||
readOnly: true
|
||||
uniqueItems: true
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
from six import StringIO
|
||||
|
||||
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 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,13 @@
|
||||
import json
|
||||
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
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
from drf_yasg.inspectors import FieldInspector, FilterInspector, PaginatorInspector, SerializerInspector
|
||||
|
||||
|
||||
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,13 +26,23 @@ class NoOpPaginatorInspector(PaginatorInspector):
|
||||
pass
|
||||
|
||||
|
||||
def test_noop_inspectors(swagger_settings, swagger_dict, reference_schema):
|
||||
def test_noop_inspectors(swagger_settings, mock_schema_request, codec_json, reference_schema, compare_schemas):
|
||||
from drf_yasg import app_settings
|
||||
|
||||
def set_inspectors(inspectors, setting_name):
|
||||
inspectors = [__name__ + '.' + inspector.__name__ for inspector in inspectors]
|
||||
swagger_settings[setting_name] = inspectors + app_settings.SWAGGER_DEFAULTS[setting_name]
|
||||
|
||||
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)
|
||||
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title="Test generator", default_version="v1"),
|
||||
version="v2",
|
||||
)
|
||||
swagger = generator.get_schema(mock_schema_request, True)
|
||||
|
||||
json_bytes = codec_json.encode(swagger)
|
||||
swagger_dict = json.loads(json_bytes.decode('utf-8'), object_pairs_hook=OrderedDict)
|
||||
compare_schemas(swagger_dict, reference_schema)
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
|
||||
from drf_yasg import openapi, codecs
|
||||
from drf_yasg import codecs, openapi
|
||||
from drf_yasg.codecs import yaml_sane_load
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.conf.urls import url
|
||||
from django.conf.urls import include
|
||||
from django.conf.urls import include, url
|
||||
from rest_framework import permissions
|
||||
|
||||
import testproj.urls
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.conf.urls import url
|
||||
from rest_framework import fields
|
||||
|
||||
from snippets.serializers import SnippetSerializer
|
||||
|
||||
from .ns_version1 import SnippetList as SnippetListV1
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from django.conf.urls import url, include
|
||||
from django.conf.urls import include, url
|
||||
from rest_framework import versioning
|
||||
|
||||
from testproj.urls import SchemaView
|
||||
|
||||
from . import ns_version1, ns_version2
|
||||
|
||||
VERSION_PREFIX_NS = r"^versioned/ns/"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.conf.urls import url
|
||||
from rest_framework import generics, versioning, fields
|
||||
from rest_framework import fields, generics, versioning
|
||||
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
|
||||
@@ -39,6 +39,7 @@ pip_pre = True
|
||||
skip_install = true
|
||||
deps =
|
||||
flake8
|
||||
flake8-isort
|
||||
commands =
|
||||
flake8 src/drf_yasg testproj tests setup.py
|
||||
|
||||
@@ -56,3 +57,19 @@ python_paths = testproj
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
exclude = **/migrations/*
|
||||
|
||||
[isort]
|
||||
skip = .eggs,.tox,docs
|
||||
skip_glob = **/migrations/*
|
||||
not_skip = __init__.py
|
||||
atomic = true
|
||||
multi_line_output = 5
|
||||
line_length = 120
|
||||
known_future_library = future,six
|
||||
known_standard_library =
|
||||
collections,copy,distutils,functools,inspect,io,json,logging,operator,os,pkg_resources,re,setuptools,sys,
|
||||
types,warnings
|
||||
known_third_party =
|
||||
coreapi,coreschema,datadiff,django,django_filters,djangorestframework_camel_case,flex,inflection,pygments,
|
||||
pytest,rest_framework,ruamel,setuptools_scm,swagger_spec_validator,uritemplate
|
||||
known_first_party = drf_yasg,testproj,articles,snippets,users,urlconfs
|
||||
|
||||
Reference in New Issue
Block a user