Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2c21539f7 | |||
| 73bd7a136d | |||
| bdf7e8a4ae | |||
| 174f1153b4 | |||
| af2a44e1e9 | |||
| f6a535eb45 |
-1
@@ -54,7 +54,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PyShadowingNamesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="PyShadowingNamesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PyUnusedLocalInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
<inspection_tool class="PyUnusedLocalInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||||
<option name="ignoreTupleUnpacking" value="true" />
|
<option name="ignoreTupleUnpacking" value="true" />
|
||||||
<option name="ignoreLambdaParameters" value="true" />
|
<option name="ignoreLambdaParameters" value="true" />
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ You want to contribute some code? Great! Here are a few steps to get you started
|
|||||||
|
|
||||||
#. 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 requrests are encouraged, because they allow you to get feedback and help more
|
Incomplete/Work In Progress pull requests are encouraged, because they allow you to get feedback and help more
|
||||||
easily.
|
easily.
|
||||||
|
|
||||||
#. Your code must pass all the required travis jobs before it is merged. As of now, this includes running on
|
#. Your code must pass all the required travis jobs before it is merged. As of now, this includes running on
|
||||||
|
|||||||
+9
-1
@@ -5,7 +5,7 @@
|
|||||||
drf-yasg - Yet another Swagger generator
|
drf-yasg - Yet another Swagger generator
|
||||||
########################################
|
########################################
|
||||||
|
|
||||||
|travis| |nbsp| |codecov|
|
|travis| |nbsp| |codecov| |nbsp| |rtd-badge| |nbsp| |pypi-version|
|
||||||
|
|
||||||
Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framework API.
|
Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framework API.
|
||||||
|
|
||||||
@@ -358,5 +358,13 @@ https://drf-yasg.readthedocs.io/en/latest/
|
|||||||
:target: https://codecov.io/gh/axnsan12/drf-yasg
|
:target: https://codecov.io/gh/axnsan12/drf-yasg
|
||||||
:alt: Codecov
|
:alt: Codecov
|
||||||
|
|
||||||
|
.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg
|
||||||
|
:target: https://pypi.org/project/drf-yasg/
|
||||||
|
:alt: PyPI
|
||||||
|
|
||||||
|
.. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg
|
||||||
|
:target: https://drf-yasg.readthedocs.io/
|
||||||
|
:alt: ReadTheDocs
|
||||||
|
|
||||||
.. |nbsp| unicode:: 0xA0
|
.. |nbsp| unicode:: 0xA0
|
||||||
:trim:
|
:trim:
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#########
|
||||||
|
Changelog
|
||||||
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.0.4**
|
||||||
|
*********
|
||||||
|
|
||||||
|
- **FIX:** fixed improper generation of YAML references
|
||||||
|
- **FEATURE:** added ``query_serializer`` parameter to
|
||||||
|
:func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`)
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.0.3**
|
||||||
|
*********
|
||||||
|
|
||||||
|
- **FIX:** fixed bug that caused schema views returned from cache to fail (:issue:`14`)
|
||||||
|
- **FIX:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by
|
||||||
|
attempting to shove file parameters into Schema objects
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.0.2**
|
||||||
|
*********
|
||||||
|
|
||||||
|
- First published version
|
||||||
+49
-1
@@ -7,6 +7,9 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import sphinx_rtd_theme
|
import sphinx_rtd_theme
|
||||||
|
from docutils import nodes, utils
|
||||||
|
from docutils.parsers.rst import roles
|
||||||
|
from docutils.parsers.rst.roles import set_classes
|
||||||
from pkg_resources import get_distribution
|
from pkg_resources import get_distribution
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
@@ -193,6 +196,11 @@ nitpick_ignore = [
|
|||||||
('py:obj', 'APIView'),
|
('py:obj', 'APIView'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# even though the package should be already installed, the sphinx build on RTD
|
||||||
|
# for some reason needs the sources dir to be in the path in order for viewcode to work
|
||||||
|
sys.path.insert(0, os.path.abspath('../src'))
|
||||||
|
|
||||||
|
# activate the Django testproj to be able to succesfully import drf_yasg
|
||||||
sys.path.insert(0, os.path.abspath('../testproj'))
|
sys.path.insert(0, os.path.abspath('../testproj'))
|
||||||
os.putenv('DJANGO_SETTINGS_MODULE', 'testproj.settings')
|
os.putenv('DJANGO_SETTINGS_MODULE', 'testproj.settings')
|
||||||
|
|
||||||
@@ -200,7 +208,47 @@ from django.conf import settings # noqa: E402
|
|||||||
|
|
||||||
settings.configure()
|
settings.configure()
|
||||||
|
|
||||||
|
# instantiate a SchemaView in the views module to make it available to autodoc
|
||||||
import drf_yasg.views # noqa: E402
|
import drf_yasg.views # noqa: E402
|
||||||
|
|
||||||
# instantiate a SchemaView in the views module to make it available to autodoc
|
|
||||||
drf_yasg.views.SchemaView = drf_yasg.views.get_schema_view(None)
|
drf_yasg.views.SchemaView = drf_yasg.views.get_schema_view(None)
|
||||||
|
|
||||||
|
# custom interpreted role for linking to GitHub issues and pull requests
|
||||||
|
# use as :issue:`14` or :pr:`17`
|
||||||
|
gh_issue_uri = "https://github.com/axnsan12/drf-yasg/issues/%d"
|
||||||
|
gh_pr_uri = "https://github.com/axnsan12/drf-yasg/pull/%d"
|
||||||
|
|
||||||
|
|
||||||
|
def role_github_pull_request_or_issue(name, rawtext, text, lineno, inliner, options=None, content=None):
|
||||||
|
options = options or {}
|
||||||
|
content = content or []
|
||||||
|
try:
|
||||||
|
ghid = int(text)
|
||||||
|
if ghid <= 0:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
msg = inliner.reporter.error(
|
||||||
|
'GitHub pull request or issue number must be a number greater than or equal to 1; '
|
||||||
|
'"%s" is invalid.' % text, line=lineno
|
||||||
|
)
|
||||||
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
|
return [prb], [msg]
|
||||||
|
# Base URL mainly used by inliner.rfc_reference, so this is correct:
|
||||||
|
|
||||||
|
if name == 'pr':
|
||||||
|
ref = gh_pr_uri
|
||||||
|
elif name == 'issue':
|
||||||
|
ref = gh_issue_uri
|
||||||
|
else:
|
||||||
|
msg = inliner.reporter.error('unknown tag name for GitHub reference - "%s"' % name, line=lineno)
|
||||||
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
|
return [prb], [msg]
|
||||||
|
|
||||||
|
ref = ref % ghid
|
||||||
|
set_classes(options)
|
||||||
|
node = nodes.reference(rawtext, '#' + utils.unescape(text), refuri=ref, **options)
|
||||||
|
return [node], []
|
||||||
|
|
||||||
|
|
||||||
|
roles.register_local_role('pr', role_github_pull_request_or_issue)
|
||||||
|
roles.register_local_role('issue', role_github_pull_request_or_issue)
|
||||||
|
|||||||
+80
-3
@@ -50,9 +50,86 @@ is structured, starting from the root ``Swagger`` object.
|
|||||||
+ ``operationId`` - should be unique across all operations
|
+ ``operationId`` - should be unique across all operations
|
||||||
+ ``tags`` - used to group operations in the listing
|
+ ``tags`` - used to group operations in the listing
|
||||||
|
|
||||||
It is interesting to note that the main difference between ``Parameter`` and ``Schema`` is that Schemas can nest other
|
It is interesting to note the main differences between :class:`.Parameter` and :class:`.Schema` objects:
|
||||||
Schemas, while Parameters are "primitives" and cannot contain other Parameters. The only exception are ``body``
|
|
||||||
Parameters, which can contain a Schema.
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| :class:`.Schema` | :class:`.Parameter` |
|
||||||
|
+==========================================================+===========================================================+
|
||||||
|
| Can nest other Schemas | Cannot nest other Parameters |br| |
|
||||||
|
| | Can only nest a Schema if the parameter is ``in: body`` |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Cannot describe file uploads |br| | Can describe file uploads via ``type`` = ``file``, |br| |
|
||||||
|
| - ``file`` is not permitted as a value for ``type`` | but only as part of a form :class:`.Operation` [#formop]_ |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Can be used in :class:`.Response`\ s | Cannot be used in :class:`.Response`\ s |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Cannot be used in form :class:`.Operation`\ s [#formop]_ | Can be used in form :class:`.Operation`\ s [#formop]_ |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Can only describe request or response bodies | Can describe ``query``, ``form``, ``header`` or ``path`` |
|
||||||
|
| | parameters |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
|
||||||
|
.. [#formop] a form Operation is an :class:`.Operation` that consumes ``multipart/form-data`` or
|
||||||
|
``application/x-www-form-urlencoded`` content
|
||||||
|
|
||||||
|
* a form Operation cannot have ``body`` parameters
|
||||||
|
* a non-form operation cannot have ``form`` parameters
|
||||||
|
|
||||||
|
****************
|
||||||
|
Default behavior
|
||||||
|
****************
|
||||||
|
|
||||||
|
This section describes where information is sourced from when using the default generation process.
|
||||||
|
|
||||||
|
* :class:`.Paths` are generated by exploring the patterns registered in your default ``urlconf``, or the ``patterns``
|
||||||
|
and ``urlconf`` you specified when constructing :class:`.OpenAPISchemaGenerator`; only views inheriting from Django
|
||||||
|
Rest Framework's ``APIView`` are looked at, all other views are ignored
|
||||||
|
* ``path`` :class:`.Parameter`\ s are generated by looking in the URL pattern for any template parameters; attempts are
|
||||||
|
made to guess their type from the views ``queryset`` and ``lookup_field``, if applicable. You can override path
|
||||||
|
parameters via ``manual_parameters`` in :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
|
||||||
|
* ``query`` :class:`.Parameter`\ s - i.e. parameters specified in the URL as ``/path/?query1=value&query2=value`` -
|
||||||
|
are generated from your view's ``filter_backends`` and ``paginator``, if any are declared. Additional parameters can
|
||||||
|
be specified via the ``query_serializer`` and ``manual_parameters`` arguments of
|
||||||
|
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`
|
||||||
|
* The request body is only generated for the HTTP ``POST``, ``PUT`` and ``PATCH`` methods, and is sourced from the
|
||||||
|
view's ``serializer_class``. You can also override the request body using the ``request_body`` argument of
|
||||||
|
:ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>`.
|
||||||
|
|
||||||
|
- if the view represents a form request (that is, all its parsers are of the ``multipart/form-data`` or
|
||||||
|
``application/x-www-form-urlencoded`` media types), the request body will be output as ``form``
|
||||||
|
:class:`.Parameter`\ s
|
||||||
|
- if it is not a form request, the request body will be output as a single ``body`` :class:`.Parameter` wrapped
|
||||||
|
around a :class:`.Schema`
|
||||||
|
|
||||||
|
* ``header`` :class:`.Parameter`\ s are supported by the OpenAPI specification but are never generated by this library;
|
||||||
|
you can still add them using ``manual_parameters``.
|
||||||
|
* :class:`.Responses` are generated as follows:
|
||||||
|
|
||||||
|
+ if ``responses`` is provided to :ref:`@swagger_auto_schema <custom-spec-swagger-auto-schema>` and contains at least
|
||||||
|
one success status code (i.e. any `2xx` status code), no automatic response is generated and the given response
|
||||||
|
is used as described in the :func:`@swagger_auto_schema documentation <.swagger_auto_schema>`
|
||||||
|
+ otherwise, an attempt is made to generate a default response:
|
||||||
|
|
||||||
|
- the success status code is assumed to be ``204` for ``DELETE`` requests, ``201`` for ``POST`` requests, and
|
||||||
|
``200`` for all other request methods
|
||||||
|
- if the view has a request body, the same ``Serializer`` or :class:`.Schema` as in the request body is used
|
||||||
|
in generating the :class:`.Response` schema; this is inline with the default ``GenericAPIView`` and
|
||||||
|
``GenericViewSet`` behavior
|
||||||
|
- if the view has no request body, its ``serializer_class`` is used to generate the :class:`.Response` schema
|
||||||
|
- if the view is a list view (as defined by :func:`.is_list_view`), the response schema is wrapped in an array
|
||||||
|
- if the view is also paginated, the response schema is then wrapped in the appropriate paging response structure
|
||||||
|
- the description of the response is left blank
|
||||||
|
|
||||||
|
* :class:`.Response` headers are supported by the OpenAPI specification but not currently supported by this library;
|
||||||
|
you can still add them manually by providing an `appropriately structured dictionary
|
||||||
|
<https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#headersObject>`_
|
||||||
|
to the ``headers`` property of a :class:`.Response` object
|
||||||
|
* *descriptions* for :class:`.Operation`\ s, :class:`.Parameter`\ s and :class:`.Schema`\ s are picked up from
|
||||||
|
docstrings and ``help_text`` attributes in the same manner as the `default DRF SchemaGenerator
|
||||||
|
<http://www.django-rest-framework.org/api-guide/schemas/#schemas-as-documentation>`_
|
||||||
|
|
||||||
|
|
||||||
|
.. _custom-spec-swagger-auto-schema:
|
||||||
|
|
||||||
**************************************
|
**************************************
|
||||||
The ``@swagger_auto_schema`` decorator
|
The ``@swagger_auto_schema`` decorator
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ drf\_yasg\.openapi
|
|||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
:exclude-members: _bare_SwaggerDict
|
||||||
|
|
||||||
drf\_yasg\.renderers
|
drf\_yasg\.renderers
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ drf-yasg
|
|||||||
settings.rst
|
settings.rst
|
||||||
contributing.rst
|
contributing.rst
|
||||||
license.rst
|
license.rst
|
||||||
|
changelog.rst
|
||||||
|
|
||||||
Source code documentation
|
Source code documentation
|
||||||
=========================
|
=========================
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import sys
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
import distutils.core
|
import distutils.core
|
||||||
|
|
||||||
@@ -21,23 +22,24 @@ def _install_setup_requires(attrs):
|
|||||||
dist.fetch_build_eggs(dist.setup_requires)
|
dist.fetch_build_eggs(dist.setup_requires)
|
||||||
|
|
||||||
|
|
||||||
try:
|
if 'sdist' in sys.argv:
|
||||||
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
|
try:
|
||||||
# (setuptools_scm adds find_files hooks into setuptools on install)
|
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
|
||||||
_install_setup_requires({'setup_requires': requirements_setup})
|
# (setuptools_scm adds find_files hooks into setuptools on install)
|
||||||
except Exception:
|
_install_setup_requires({'setup_requires': requirements_setup})
|
||||||
pass
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# see https://github.com/pypa/setuptools_scm/issues/190, setuptools_scm includes ALL versioned files from the git
|
# see https://github.com/pypa/setuptools_scm/issues/190, setuptools_scm includes ALL versioned files from
|
||||||
# repo into the sdist by default, and there is no easy way to provide an opt-out;
|
# the git repo into the sdist by default, and there is no easy way to provide an opt-out;
|
||||||
# this hack is ugly but does the job; because this is not really a documented interface of the module,
|
# this hack is ugly but does the job; because this is not really a documented interface of the module,
|
||||||
# the setuptools_scm version should remain pinned to ensure it keeps working
|
# the setuptools_scm version should remain pinned to ensure it keeps working
|
||||||
import setuptools_scm.integration
|
import setuptools_scm.integration
|
||||||
|
|
||||||
setuptools_scm.integration.find_files = lambda _: []
|
setuptools_scm.integration.find_files = lambda _: []
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def read_req(req_file):
|
def read_req(req_file):
|
||||||
|
|||||||
+22
-2
@@ -87,7 +87,7 @@ class _OpenAPICodec(object):
|
|||||||
:rtype: OrderedDict
|
:rtype: OrderedDict
|
||||||
"""
|
"""
|
||||||
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
||||||
return swagger
|
return swagger.as_odict()
|
||||||
|
|
||||||
|
|
||||||
class OpenAPICodecJson(_OpenAPICodec):
|
class OpenAPICodecJson(_OpenAPICodec):
|
||||||
@@ -101,6 +101,10 @@ class OpenAPICodecJson(_OpenAPICodec):
|
|||||||
class SaneYamlDumper(yaml.SafeDumper):
|
class SaneYamlDumper(yaml.SafeDumper):
|
||||||
"""YamlDumper class usable for dumping ``OrderedDict`` and list instances in a standard way."""
|
"""YamlDumper class usable for dumping ``OrderedDict`` and list instances in a standard way."""
|
||||||
|
|
||||||
|
def ignore_aliases(self, data):
|
||||||
|
"""Disable YAML references."""
|
||||||
|
return True
|
||||||
|
|
||||||
def increase_indent(self, flow=False, indentless=False, **kwargs):
|
def increase_indent(self, flow=False, indentless=False, **kwargs):
|
||||||
"""https://stackoverflow.com/a/39681672
|
"""https://stackoverflow.com/a/39681672
|
||||||
|
|
||||||
@@ -146,9 +150,25 @@ SaneYamlDumper.add_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
|||||||
SaneYamlDumper.add_multi_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
SaneYamlDumper.add_multi_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_sane_dump(data, binary):
|
||||||
|
"""Dump the given data dictionary into a sane format:
|
||||||
|
|
||||||
|
* OrderedDicts are dumped as regular mappings instead of non-standard !!odict
|
||||||
|
* multi-line mapping style instead of json-like inline style
|
||||||
|
* list elements are indented into their parents
|
||||||
|
* YAML references/aliases are disabled
|
||||||
|
|
||||||
|
:param dict data: the data to be serializers
|
||||||
|
:param bool binary: True to return a utf-8 encoded binary object, False to return a string
|
||||||
|
:return: the serialized YAML
|
||||||
|
:rtype: str,bytes
|
||||||
|
"""
|
||||||
|
return yaml.dump(data, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8' if binary else None)
|
||||||
|
|
||||||
|
|
||||||
class OpenAPICodecYaml(_OpenAPICodec):
|
class OpenAPICodecYaml(_OpenAPICodec):
|
||||||
media_type = 'application/yaml'
|
media_type = 'application/yaml'
|
||||||
|
|
||||||
def _dump_dict(self, spec):
|
def _dump_dict(self, spec):
|
||||||
"""Dump ``spec`` into YAML."""
|
"""Dump ``spec`` into YAML."""
|
||||||
return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8')
|
return yaml_sane_dump(spec, binary=True)
|
||||||
|
|||||||
+65
-21
@@ -10,7 +10,7 @@ from rest_framework.viewsets import GenericViewSet
|
|||||||
|
|
||||||
from . import openapi
|
from . import openapi
|
||||||
from .errors import SwaggerGenerationError
|
from .errors import SwaggerGenerationError
|
||||||
from .utils import serializer_field_to_swagger, no_body, is_list_view
|
from .utils import serializer_field_to_swagger, no_body, is_list_view, param_list_to_odict
|
||||||
|
|
||||||
|
|
||||||
def force_serializer_instance(serializer):
|
def force_serializer_instance(serializer):
|
||||||
@@ -30,6 +30,8 @@ def force_serializer_instance(serializer):
|
|||||||
|
|
||||||
|
|
||||||
class SwaggerAutoSchema(object):
|
class SwaggerAutoSchema(object):
|
||||||
|
body_methods = ('PUT', 'PATCH', 'POST') #: methods allowed to have a request body
|
||||||
|
|
||||||
def __init__(self, view, path, method, overrides, components):
|
def __init__(self, view, path, method, overrides, components):
|
||||||
"""Inspector class responsible for providing :class:`.Operation` definitions given a
|
"""Inspector class responsible for providing :class:`.Operation` definitions given a
|
||||||
|
|
||||||
@@ -88,10 +90,6 @@ class SwaggerAutoSchema(object):
|
|||||||
:return: a (potentially empty) list of :class:`.Parameter`\ s either ``in: body`` or ``in: formData``
|
:return: a (potentially empty) list of :class:`.Parameter`\ s either ``in: body`` or ``in: formData``
|
||||||
:rtype: list[openapi.Parameter]
|
:rtype: list[openapi.Parameter]
|
||||||
"""
|
"""
|
||||||
# only PUT, PATCH or POST can have a request body
|
|
||||||
if self.method not in ('PUT', 'PATCH', 'POST'):
|
|
||||||
return []
|
|
||||||
|
|
||||||
serializer = self.get_request_serializer()
|
serializer = self.get_request_serializer()
|
||||||
schema = None
|
schema = None
|
||||||
if serializer is None:
|
if serializer is None:
|
||||||
@@ -109,6 +107,15 @@ class SwaggerAutoSchema(object):
|
|||||||
schema = self.get_request_body_schema(serializer)
|
schema = self.get_request_body_schema(serializer)
|
||||||
return [self.make_body_parameter(schema)]
|
return [self.make_body_parameter(schema)]
|
||||||
|
|
||||||
|
def get_view_serializer(self):
|
||||||
|
"""Return the serializer as defined by the view's ``get_serializer()`` method.
|
||||||
|
|
||||||
|
:return: the view's ``Serializer``
|
||||||
|
"""
|
||||||
|
if not hasattr(self.view, 'get_serializer'):
|
||||||
|
return None
|
||||||
|
return self.view.get_serializer()
|
||||||
|
|
||||||
def get_request_serializer(self):
|
def get_request_serializer(self):
|
||||||
"""Return the request serializer (used for parsing the request payload) for this endpoint.
|
"""Return the request serializer (used for parsing the request payload) for this endpoint.
|
||||||
|
|
||||||
@@ -119,13 +126,16 @@ class SwaggerAutoSchema(object):
|
|||||||
if body_override is not None:
|
if body_override is not None:
|
||||||
if body_override is no_body:
|
if body_override is no_body:
|
||||||
return None
|
return None
|
||||||
|
if self.method not in self.body_methods:
|
||||||
|
raise SwaggerGenerationError("request_body can only be applied to PUT, PATCH or POST views; "
|
||||||
|
"are you looking for query_serializer or manual_parameters?")
|
||||||
if isinstance(body_override, openapi.Schema.OR_REF):
|
if isinstance(body_override, openapi.Schema.OR_REF):
|
||||||
return body_override
|
return body_override
|
||||||
return force_serializer_instance(body_override)
|
return force_serializer_instance(body_override)
|
||||||
else:
|
elif self.method in self.body_methods:
|
||||||
if not hasattr(self.view, 'get_serializer'):
|
return self.get_view_serializer()
|
||||||
return None
|
|
||||||
return self.view.get_serializer()
|
return None
|
||||||
|
|
||||||
def get_request_form_parameters(self, serializer):
|
def get_request_form_parameters(self, serializer):
|
||||||
"""Given a Serializer, return a list of ``in: formData`` :class:`.Parameter`\ s.
|
"""Given a Serializer, return a list of ``in: formData`` :class:`.Parameter`\ s.
|
||||||
@@ -133,12 +143,7 @@ class SwaggerAutoSchema(object):
|
|||||||
:param serializer: the view's request serializer as returned by :meth:`.get_request_serializer`
|
:param serializer: the view's request serializer as returned by :meth:`.get_request_serializer`
|
||||||
:rtype: list[openapi.Parameter]
|
:rtype: list[openapi.Parameter]
|
||||||
"""
|
"""
|
||||||
fields = getattr(serializer, 'fields', {})
|
return self.serializer_to_parameters(serializer, in_=openapi.IN_FORM)
|
||||||
return [
|
|
||||||
self.field_to_parameter(value, key, openapi.IN_FORM)
|
|
||||||
for key, value
|
|
||||||
in fields.items()
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_request_body_schema(self, serializer):
|
def get_request_body_schema(self, serializer):
|
||||||
"""Return the :class:`.Schema` for a given request's body data. Only applies to PUT, PATCH and POST requests.
|
"""Return the :class:`.Schema` for a given request's body data. Only applies to PUT, PATCH and POST requests.
|
||||||
@@ -163,7 +168,7 @@ class SwaggerAutoSchema(object):
|
|||||||
:return: modified parameters
|
:return: modified parameters
|
||||||
:rtype: list[openapi.Parameter]
|
:rtype: list[openapi.Parameter]
|
||||||
"""
|
"""
|
||||||
parameters = OrderedDict(((param.name, param.in_), param) for param in parameters)
|
parameters = param_list_to_odict(parameters)
|
||||||
manual_parameters = self.overrides.get('manual_parameters', None) or []
|
manual_parameters = self.overrides.get('manual_parameters', None) or []
|
||||||
|
|
||||||
if any(param.in_ == openapi.IN_BODY for param in manual_parameters): # pragma: no cover
|
if any(param.in_ == openapi.IN_BODY for param in manual_parameters): # pragma: no cover
|
||||||
@@ -173,7 +178,7 @@ class SwaggerAutoSchema(object):
|
|||||||
raise SwaggerGenerationError("cannot add form parameters when the request has a request schema; "
|
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?")
|
"did you forget to set an appropriate parser class on the view?")
|
||||||
|
|
||||||
parameters.update(((param.name, param.in_), param) for param in manual_parameters)
|
parameters.update(param_list_to_odict(manual_parameters))
|
||||||
return list(parameters.values())
|
return list(parameters.values())
|
||||||
|
|
||||||
def get_responses(self):
|
def get_responses(self):
|
||||||
@@ -218,13 +223,15 @@ class SwaggerAutoSchema(object):
|
|||||||
default_schema = ''
|
default_schema = ''
|
||||||
if method == 'post':
|
if method == 'post':
|
||||||
default_status = status.HTTP_201_CREATED
|
default_status = status.HTTP_201_CREATED
|
||||||
default_schema = self.get_request_serializer()
|
default_schema = self.get_request_serializer() or self.get_view_serializer()
|
||||||
elif method == 'delete':
|
elif method == 'delete':
|
||||||
default_status = status.HTTP_204_NO_CONTENT
|
default_status = status.HTTP_204_NO_CONTENT
|
||||||
elif method in ('get', 'put', 'patch'):
|
elif method in ('get', 'put', 'patch'):
|
||||||
default_schema = self.get_request_serializer()
|
default_schema = self.get_request_serializer() or self.get_view_serializer()
|
||||||
|
|
||||||
default_schema = default_schema or ''
|
default_schema = default_schema or ''
|
||||||
|
if any(is_form_media_type(encoding) for encoding in self.get_consumes()):
|
||||||
|
default_schema = ''
|
||||||
if default_schema:
|
if default_schema:
|
||||||
if not isinstance(default_schema, openapi.Schema):
|
if not isinstance(default_schema, openapi.Schema):
|
||||||
default_schema = self.serializer_to_schema(default_schema)
|
default_schema = self.serializer_to_schema(default_schema)
|
||||||
@@ -288,12 +295,35 @@ class SwaggerAutoSchema(object):
|
|||||||
|
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
|
def get_query_serializer(self):
|
||||||
|
"""Return the query serializer (used for parsing query parameters) for this endpoint.
|
||||||
|
|
||||||
|
:return: the query serializer, or ``None``
|
||||||
|
"""
|
||||||
|
query_serializer = self.overrides.get('query_serializer', None)
|
||||||
|
if query_serializer is not None:
|
||||||
|
query_serializer = force_serializer_instance(query_serializer)
|
||||||
|
return query_serializer
|
||||||
|
|
||||||
def get_query_parameters(self):
|
def get_query_parameters(self):
|
||||||
"""Return the query parameters accepted by this view.
|
"""Return the query parameters accepted by this view.
|
||||||
|
|
||||||
:rtype: list[openapi.Parameter]
|
:rtype: list[openapi.Parameter]
|
||||||
"""
|
"""
|
||||||
return self.get_filter_parameters() + self.get_pagination_parameters()
|
natural_parameters = self.get_filter_parameters() + self.get_pagination_parameters()
|
||||||
|
|
||||||
|
query_serializer = self.get_query_serializer()
|
||||||
|
serializer_parameters = []
|
||||||
|
if query_serializer is not None:
|
||||||
|
serializer_parameters = self.serializer_to_parameters(query_serializer, in_=openapi.IN_QUERY)
|
||||||
|
|
||||||
|
if len(set(param_list_to_odict(natural_parameters)) & set(param_list_to_odict(serializer_parameters))) != 0:
|
||||||
|
raise SwaggerGenerationError(
|
||||||
|
"your query_serializer contains fields that conflict with the "
|
||||||
|
"filter_backend or paginator_class on the view - %s %s" % (self.method, self.path)
|
||||||
|
)
|
||||||
|
|
||||||
|
return natural_parameters + serializer_parameters
|
||||||
|
|
||||||
def should_filter(self):
|
def should_filter(self):
|
||||||
"""Determine whether filter backend parameters should be included for this request.
|
"""Determine whether filter backend parameters should be included for this request.
|
||||||
@@ -398,12 +428,26 @@ class SwaggerAutoSchema(object):
|
|||||||
def serializer_to_schema(self, serializer):
|
def serializer_to_schema(self, serializer):
|
||||||
"""Convert a DRF Serializer instance to an :class:`.openapi.Schema`.
|
"""Convert a DRF Serializer instance to an :class:`.openapi.Schema`.
|
||||||
|
|
||||||
:param serializers.BaseSerializer serializer:
|
:param serializers.BaseSerializer serializer: the ``Serializer`` instance
|
||||||
:rtype: openapi.Schema
|
:rtype: openapi.Schema
|
||||||
"""
|
"""
|
||||||
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
definitions = self.components.with_scope(openapi.SCHEMA_DEFINITIONS)
|
||||||
return serializer_field_to_swagger(serializer, openapi.Schema, definitions)
|
return serializer_field_to_swagger(serializer, openapi.Schema, definitions)
|
||||||
|
|
||||||
|
def serializer_to_parameters(self, serializer, in_):
|
||||||
|
"""Convert a DRF serializer into a list of :class:`.Parameter`\ s using :meth:`.field_to_parameter`
|
||||||
|
|
||||||
|
:param serializers.BaseSerializer serializer: the ``Serializer`` instance
|
||||||
|
:param str in_: the location of the parameters, one of the `openapi.IN_*` constants
|
||||||
|
:rtype: list[openapi.Parameter]
|
||||||
|
"""
|
||||||
|
fields = getattr(serializer, 'fields', {})
|
||||||
|
return [
|
||||||
|
self.field_to_parameter(value, key, in_)
|
||||||
|
for key, value
|
||||||
|
in fields.items()
|
||||||
|
]
|
||||||
|
|
||||||
def field_to_parameter(self, field, name, in_):
|
def field_to_parameter(self, field, name, in_):
|
||||||
"""Convert a DRF serializer Field to a swagger :class:`.Parameter` object.
|
"""Convert a DRF serializer Field to a swagger :class:`.Parameter` object.
|
||||||
|
|
||||||
|
|||||||
+41
-9
@@ -1,4 +1,3 @@
|
|||||||
import copy
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from coreapi.compat import urlparse
|
from coreapi.compat import urlparse
|
||||||
@@ -48,8 +47,8 @@ def make_swagger_name(attribute_name):
|
|||||||
Convert a python variable name into a Swagger spec attribute name.
|
Convert a python variable name into a Swagger spec attribute name.
|
||||||
|
|
||||||
In particular,
|
In particular,
|
||||||
* if name starts with x\_, return "x-{camelCase}"
|
* if name starts with ``x_``, return ``x-{camelCase}``
|
||||||
* if name is 'ref', return "$ref"
|
* if name is ``ref``, return ``$ref``
|
||||||
* else return the name converted to camelCase, with trailing underscores stripped
|
* else return the name converted to camelCase, with trailing underscores stripped
|
||||||
|
|
||||||
:param str attribute_name: python attribute name
|
:param str attribute_name: python attribute name
|
||||||
@@ -62,9 +61,19 @@ def make_swagger_name(attribute_name):
|
|||||||
return camelize(attribute_name.rstrip('_'), uppercase_first_letter=False)
|
return camelize(attribute_name.rstrip('_'), uppercase_first_letter=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _bare_SwaggerDict(cls):
|
||||||
|
assert issubclass(cls, SwaggerDict)
|
||||||
|
result = cls.__new__(cls)
|
||||||
|
OrderedDict.__init__(result) # no __init__ called for SwaggerDict subclasses!
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class SwaggerDict(OrderedDict):
|
class SwaggerDict(OrderedDict):
|
||||||
"""A particular type of OrderedDict, which maps all attribute accesses to dict lookups using
|
"""A particular type of OrderedDict, which maps all attribute accesses to dict lookups using
|
||||||
:func:`.make_swagger_name`. Used as a base class for all Swagger helper models.
|
:func:`.make_swagger_name`. Attribute names starting with ``_`` are set on the object as-is and are not included
|
||||||
|
in the specification output.
|
||||||
|
|
||||||
|
Used as a base class for all Swagger helper models.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **attrs):
|
def __init__(self, **attrs):
|
||||||
@@ -105,11 +114,34 @@ class SwaggerDict(OrderedDict):
|
|||||||
for attr, val in self._extras__.items():
|
for attr, val in self._extras__.items():
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
|
||||||
# noinspection PyArgumentList,PyDefaultArgument
|
@staticmethod
|
||||||
def __deepcopy__(self, memodict={}):
|
def _as_odict(obj, memo):
|
||||||
result = OrderedDict(list(self.items()))
|
"""Implementation detail of :meth:`.as_odict`"""
|
||||||
result.update(copy.deepcopy(result, memodict))
|
if id(obj) in memo:
|
||||||
return result
|
return memo[id(obj)]
|
||||||
|
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = OrderedDict()
|
||||||
|
memo[id(obj)] = result
|
||||||
|
for attr, val in obj.items():
|
||||||
|
result[attr] = SwaggerDict._as_odict(val, memo)
|
||||||
|
return result
|
||||||
|
elif isinstance(obj, (list, tuple)):
|
||||||
|
return type(obj)(SwaggerDict._as_odict(elem, memo) for elem in obj)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def as_odict(self):
|
||||||
|
"""Convert this object into an ``OrderedDict`` instance.
|
||||||
|
|
||||||
|
:rtype: OrderedDict
|
||||||
|
"""
|
||||||
|
return SwaggerDict._as_odict(self, {})
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
# for pickle supprt; this skips calls to all SwaggerDict __init__ methods and relies
|
||||||
|
# on the already set attributes instead
|
||||||
|
return _bare_SwaggerDict, (type(self),), vars(self), None, iter(self.items())
|
||||||
|
|
||||||
|
|
||||||
class Contact(SwaggerDict):
|
class Contact(SwaggerDict):
|
||||||
|
|||||||
+38
-7
@@ -45,8 +45,8 @@ def is_list_view(path, method, view):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, manual_parameters=None,
|
def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, query_serializer=None,
|
||||||
operation_description=None, responses=None):
|
manual_parameters=None, operation_description=None, responses=None):
|
||||||
"""Decorate a view method to customize the :class:`.Operation` object generated from it.
|
"""Decorate a view method to customize the :class:`.Operation` object generated from it.
|
||||||
|
|
||||||
`method` and `methods` are mutually exclusive and must only be present when decorating a view method that accepts
|
`method` and `methods` are mutually exclusive and must only be present when decorating a view method that accepts
|
||||||
@@ -67,6 +67,15 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
|||||||
If a ``Serializer`` class or instance is given, it will be automatically converted into a :class:`.Schema`
|
If a ``Serializer`` class or instance is given, it will be automatically converted into a :class:`.Schema`
|
||||||
used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\ s, as appropriate.
|
used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\ s, as appropriate.
|
||||||
|
|
||||||
|
:param .Serializer query_serializer: if you use a ``Serializer`` to parse query parameters, you can pass it here
|
||||||
|
and have :class:`.Parameter` objects be generated automatically from it.
|
||||||
|
|
||||||
|
If any ``Field`` on the serializer cannot be represented as a ``query`` :class:`.Parameter`
|
||||||
|
(e.g. nested Serializers, file fields, ...), the schema generation will fail with an error.
|
||||||
|
|
||||||
|
Schema generation will also fail if the name of any Field on the `query_serializer` conflicts with parameters
|
||||||
|
generated by ``filter_backends`` or ``paginator``.
|
||||||
|
|
||||||
:param list[.Parameter] manual_parameters: a list of manual parameters to override the automatically generated ones
|
:param list[.Parameter] manual_parameters: a list of manual parameters to override the automatically generated ones
|
||||||
|
|
||||||
:class:`.Parameter`\ s are identified by their (``name``, ``in``) combination, and any parameters given
|
:class:`.Parameter`\ s are identified by their (``name``, ``in``) combination, and any parameters given
|
||||||
@@ -94,14 +103,16 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
|||||||
data = {
|
data = {
|
||||||
'auto_schema': auto_schema,
|
'auto_schema': auto_schema,
|
||||||
'request_body': request_body,
|
'request_body': request_body,
|
||||||
|
'query_serializer': query_serializer,
|
||||||
'manual_parameters': manual_parameters,
|
'manual_parameters': manual_parameters,
|
||||||
'operation_description': operation_description,
|
'operation_description': operation_description,
|
||||||
'responses': responses,
|
'responses': responses,
|
||||||
}
|
}
|
||||||
data = {k: v for k, v in data.items() if v is not None}
|
data = {k: v for k, v in data.items() if v is not None}
|
||||||
|
|
||||||
|
# if the method is a detail_route or list_route, it will have a bind_to_methods attribute
|
||||||
bind_to_methods = getattr(view_method, 'bind_to_methods', [])
|
bind_to_methods = getattr(view_method, 'bind_to_methods', [])
|
||||||
# if the method is actually a function based view
|
# if the method is actually a function based view (@api_view), it will have a 'cls' attribute
|
||||||
view_cls = getattr(view_method, 'cls', None)
|
view_cls = getattr(view_method, 'cls', None)
|
||||||
http_method_names = getattr(view_cls, 'http_method_names', [])
|
http_method_names = getattr(view_cls, 'http_method_names', [])
|
||||||
if bind_to_methods or http_method_names:
|
if bind_to_methods or http_method_names:
|
||||||
@@ -121,11 +132,12 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
|||||||
"on multi-method %s, you must specify swagger_auto_schema on a per-method basis " \
|
"on multi-method %s, you must specify swagger_auto_schema on a per-method basis " \
|
||||||
"using one of the `method` or `methods` arguments" % _route
|
"using one of the `method` or `methods` arguments" % _route
|
||||||
assert bool(methods) != bool(method), "specify either method or methods"
|
assert bool(methods) != bool(method), "specify either method or methods"
|
||||||
|
assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \
|
||||||
|
" use `method` for a single argument"
|
||||||
if method:
|
if method:
|
||||||
_methods = [method.lower()]
|
_methods = [method.lower()]
|
||||||
else:
|
else:
|
||||||
_methods = [mth.lower() for mth in methods]
|
_methods = [mth.lower() for mth in methods]
|
||||||
assert not isinstance(_methods, str), "`methods` expects to receive; use `method` for a single arg"
|
|
||||||
assert not any(mth in existing_data for mth in _methods), "method defined multiple times"
|
assert not any(mth in existing_data for mth in _methods), "method defined multiple times"
|
||||||
assert all(mth in available_methods for mth in _methods), "method not bound to %s" % _route
|
assert all(mth in available_methods for mth in _methods), "method not bound to %s" % _route
|
||||||
|
|
||||||
@@ -134,7 +146,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
|||||||
existing_data[available_methods[0]] = data
|
existing_data[available_methods[0]] = data
|
||||||
view_method.swagger_auto_schema = existing_data
|
view_method.swagger_auto_schema = existing_data
|
||||||
else:
|
else:
|
||||||
assert methods is None, \
|
assert method is None and methods is None, \
|
||||||
"the methods argument should only be specified when decorating a detail_route or list_route; you " \
|
"the methods argument should only be specified when decorating a detail_route or list_route; you " \
|
||||||
"should also ensure that you put the swagger_auto_schema decorator AFTER (above) the _route decorator"
|
"should also ensure that you put the swagger_auto_schema decorator AFTER (above) the _route decorator"
|
||||||
view_method.swagger_auto_schema = data
|
view_method.swagger_auto_schema = data
|
||||||
@@ -274,9 +286,13 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
|||||||
elif isinstance(field, serializers.FileField):
|
elif isinstance(field, serializers.FileField):
|
||||||
# swagger 2.0 does not support specifics about file fields, so ImageFile gets no special treatment
|
# swagger 2.0 does not support specifics about file fields, so ImageFile gets no special treatment
|
||||||
# OpenAPI 3.0 does support it, so a future implementation could handle this better
|
# OpenAPI 3.0 does support it, so a future implementation could handle this better
|
||||||
|
err = SwaggerGenerationError("parameter of type file is supported only in a formData Parameter")
|
||||||
if swagger_object_type != openapi.Parameter:
|
if swagger_object_type != openapi.Parameter:
|
||||||
raise SwaggerGenerationError("parameter of type file is supported only in formData Parameter")
|
raise err # pragma: no cover
|
||||||
return SwaggerType(type=openapi.TYPE_FILE)
|
param = SwaggerType(type=openapi.TYPE_FILE)
|
||||||
|
if param['in'] != openapi.IN_FORM:
|
||||||
|
raise err # pragma: no cover
|
||||||
|
return param
|
||||||
elif isinstance(field, serializers.DictField) and swagger_object_type == openapi.Schema:
|
elif isinstance(field, serializers.DictField) and swagger_object_type == openapi.Schema:
|
||||||
child_schema = serializer_field_to_swagger(field.child, ChildSwaggerType, definitions)
|
child_schema = serializer_field_to_swagger(field.child, ChildSwaggerType, definitions)
|
||||||
return SwaggerType(
|
return SwaggerType(
|
||||||
@@ -307,3 +323,18 @@ def find_regex(regex_field):
|
|||||||
|
|
||||||
# regex_validator.regex should be a compiled re object...
|
# regex_validator.regex should be a compiled re object...
|
||||||
return getattr(getattr(regex_validator, 'regex', None), 'pattern', None)
|
return getattr(getattr(regex_validator, 'regex', None), 'pattern', None)
|
||||||
|
|
||||||
|
|
||||||
|
def param_list_to_odict(parameters):
|
||||||
|
"""Transform a list of :class:`.Parameter` objects into an ``OrderedDict`` keyed on the ``(name, in_)`` tuple of
|
||||||
|
each parameter.
|
||||||
|
|
||||||
|
Raises an ``AssertionError`` if `parameters` contains duplicate parameters (by their name + in combination).
|
||||||
|
|
||||||
|
:param list[.Parameter] parameters: the list of parameters
|
||||||
|
:return: `parameters` keyed by ``(name, in_)``
|
||||||
|
:rtype: dict[tuple(str,str),.Parameter]
|
||||||
|
"""
|
||||||
|
result = OrderedDict(((param.name, param.in_), param) for param in parameters)
|
||||||
|
assert len(result) == len(parameters), "duplicate Parameters found"
|
||||||
|
return result
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
|||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(method='get', operation_description="image GET description override")
|
@swagger_auto_schema(method='get', operation_description="image GET description override")
|
||||||
@swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer, responses={200: 'success'})
|
@swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer)
|
||||||
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))
|
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))
|
||||||
def image(self, request, slug=None):
|
def image(self, request, slug=None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ def plain_view(request):
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
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'),
|
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
|
url(r'^cached/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
||||||
|
url(r'^cached/swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
|
||||||
|
url(r'^cached/redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
||||||
|
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^snippets/', include('snippets.urls')),
|
url(r'^snippets/', include('snippets.urls')),
|
||||||
|
|||||||
@@ -12,3 +12,8 @@ class UserSerializerrr(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('id', 'username', 'email', 'snippets', 'last_connected_ip', 'last_connected_at')
|
fields = ('id', 'username', 'email', 'snippets', 'last_connected_ip', 'last_connected_at')
|
||||||
|
|
||||||
|
|
||||||
|
class UserListQuerySerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(help_text="this field is generated from a query_serializer")
|
||||||
|
is_staff = serializers.BooleanField(help_text="this one too!")
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from drf_yasg.utils import swagger_auto_schema, no_body
|
from drf_yasg.utils import swagger_auto_schema, no_body
|
||||||
from users.serializers import UserSerializerrr
|
from users.serializers import UserSerializerrr, UserListQuerySerializer
|
||||||
|
|
||||||
|
|
||||||
class UserList(APIView):
|
class UserList(APIView):
|
||||||
"""UserList cbv classdoc"""
|
"""UserList cbv classdoc"""
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: UserSerializerrr(many=True)})
|
@swagger_auto_schema(query_serializer=UserListQuerySerializer, responses={200: UserSerializerrr(many=True)})
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer = UserSerializerrr(queryset, many=True)
|
serializer = UserSerializerrr(queryset, many=True)
|
||||||
|
|||||||
+2
-2
@@ -45,8 +45,8 @@ def validate_schema():
|
|||||||
from flex.core import parse as validate_flex
|
from flex.core import parse as validate_flex
|
||||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||||
|
|
||||||
validate_flex(swagger)
|
validate_flex(copy.deepcopy(swagger))
|
||||||
validate_ssv(swagger)
|
validate_ssv(copy.deepcopy(swagger))
|
||||||
|
|
||||||
return validate_schema
|
return validate_schema
|
||||||
|
|
||||||
|
|||||||
+21
-7
@@ -187,8 +187,6 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/Article'
|
|
||||||
consumes:
|
consumes:
|
||||||
- multipart/form-data
|
- multipart/form-data
|
||||||
tags:
|
tags:
|
||||||
@@ -221,8 +219,8 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: file
|
type: file
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'201':
|
||||||
description: success
|
description: ''
|
||||||
consumes:
|
consumes:
|
||||||
- multipart/form-data
|
- multipart/form-data
|
||||||
tags:
|
tags:
|
||||||
@@ -353,7 +351,17 @@ paths:
|
|||||||
get:
|
get:
|
||||||
operationId: users_list
|
operationId: users_list
|
||||||
description: UserList cbv classdoc
|
description: UserList cbv classdoc
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: username
|
||||||
|
in: query
|
||||||
|
description: this field is generated from a query_serializer
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- name: is_staff
|
||||||
|
in: query
|
||||||
|
description: this one too!
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
@@ -372,7 +380,7 @@ paths:
|
|||||||
- name: data
|
- name: data
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema: &id001
|
schema:
|
||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
type: object
|
type: object
|
||||||
@@ -382,7 +390,13 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
description: ''
|
description: ''
|
||||||
schema: *id001
|
schema:
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -40,10 +40,18 @@ def test_json_codec_roundtrip(codec_json, generator, validate_schema):
|
|||||||
def test_yaml_codec_roundtrip(codec_yaml, generator, validate_schema):
|
def test_yaml_codec_roundtrip(codec_yaml, generator, validate_schema):
|
||||||
swagger = generator.get_schema(None, True)
|
swagger = generator.get_schema(None, True)
|
||||||
yaml_bytes = codec_yaml.encode(swagger)
|
yaml_bytes = codec_yaml.encode(swagger)
|
||||||
assert b'omap' not in yaml_bytes
|
assert b'omap' not in yaml_bytes # ensure no ugly !!omap is outputted
|
||||||
|
assert b'&id' not in yaml_bytes and b'*id' not in yaml_bytes # ensure no YAML references are generated
|
||||||
validate_schema(yaml.safe_load(yaml_bytes.decode('utf-8')))
|
validate_schema(yaml.safe_load(yaml_bytes.decode('utf-8')))
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_and_json_match(codec_yaml, codec_json, generator):
|
||||||
|
swagger = generator.get_schema(None, True)
|
||||||
|
yaml_schema = yaml.safe_load(codec_yaml.encode(swagger).decode('utf-8'))
|
||||||
|
json_schema = json.loads(codec_json.encode(swagger).decode('utf-8'))
|
||||||
|
assert yaml_schema == json_schema
|
||||||
|
|
||||||
|
|
||||||
def test_basepath_only():
|
def test_basepath_only():
|
||||||
generator = OpenAPISchemaGenerator(
|
generator = OpenAPISchemaGenerator(
|
||||||
info=openapi.Info(title="Test generator", default_version="v1"),
|
info=openapi.Info(title="Test generator", default_version="v1"),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from ruamel import yaml
|
from ruamel import yaml
|
||||||
@@ -46,6 +47,26 @@ def test_redoc(client, validate_schema):
|
|||||||
_validate_text_schema_view(client, validate_schema, '/redoc/?format=openapi', json.loads)
|
_validate_text_schema_view(client, validate_schema, '/redoc/?format=openapi', json.loads)
|
||||||
|
|
||||||
|
|
||||||
|
def test_caching(client, validate_schema):
|
||||||
|
prev_schema = None
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
_validate_ui_schema_view(client, '/cached/redoc/', 'redoc/redoc.min.js')
|
||||||
|
_validate_text_schema_view(client, validate_schema, '/cached/redoc/?format=openapi', json.loads)
|
||||||
|
_validate_ui_schema_view(client, '/cached/swagger/', 'swagger-ui-dist/swagger-ui-bundle.js')
|
||||||
|
_validate_text_schema_view(client, validate_schema, '/cached/swagger/?format=openapi', json.loads)
|
||||||
|
|
||||||
|
json_schema = client.get('/cached/swagger.json')
|
||||||
|
assert json_schema.status_code == 200
|
||||||
|
json_schema = json.loads(json_schema.content.decode('utf-8'), object_pairs_hook=OrderedDict)
|
||||||
|
if prev_schema is None:
|
||||||
|
validate_schema(json_schema)
|
||||||
|
prev_schema = json_schema
|
||||||
|
else:
|
||||||
|
from datadiff.tools import assert_equal
|
||||||
|
assert_equal(prev_schema, json_schema)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.urls('urlconfs.non_public_urls')
|
@pytest.mark.urls('urlconfs.non_public_urls')
|
||||||
def test_non_public(client):
|
def test_non_public(client):
|
||||||
response = client.get('/private/swagger.yaml')
|
response = client.get('/private/swagger.yaml')
|
||||||
|
|||||||
Reference in New Issue
Block a user