Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7683a28816 | |||
| 260a994baf | |||
| 6060192a89 | |||
| 06a461ec09 | |||
| cae07f3eaf | |||
| 16b697b40d | |||
| 8a0d5a964d | |||
| 521172c195 | |||
| f6c30181fe | |||
| da4b7d4e74 | |||
| 6346d855cf | |||
| 53593af36f | |||
| 738326ac43 | |||
| 8a5be407e2 | |||
| a2c21539f7 | |||
| 73bd7a136d | |||
| bdf7e8a4ae | |||
| 174f1153b4 | |||
| af2a44e1e9 | |||
| f6a535eb45 |
+2
-1
@@ -11,6 +11,7 @@ coverage:
|
||||
default:
|
||||
enabled: yes
|
||||
target: auto
|
||||
threshold: 0%
|
||||
if_no_uploads: error
|
||||
if_ci_failed: error
|
||||
|
||||
@@ -18,7 +19,7 @@ coverage:
|
||||
default:
|
||||
enabled: yes
|
||||
target: 80%
|
||||
threshold: 60%
|
||||
threshold: 0%
|
||||
if_no_uploads: error
|
||||
if_ci_failed: error
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
-1
@@ -54,7 +54,6 @@
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<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">
|
||||
<option name="ignoreTupleUnpacking" value="true" />
|
||||
<option name="ignoreLambdaParameters" value="true" />
|
||||
|
||||
@@ -40,3 +40,8 @@ after_success:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: always
|
||||
on_failure: always
|
||||
|
||||
+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
|
||||
|
||||
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.
|
||||
|
||||
#. 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
|
||||
########################################
|
||||
|
||||
|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.
|
||||
|
||||
@@ -358,5 +358,13 @@ https://drf-yasg.readthedocs.io/en/latest/
|
||||
:target: https://codecov.io/gh/axnsan12/drf-yasg
|
||||
:alt: Codecov
|
||||
|
||||
.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg
|
||||
:target: https://pypi.python.org/pypi/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
|
||||
:trim:
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#########
|
||||
Changelog
|
||||
#########
|
||||
|
||||
|
||||
*********
|
||||
**1.0.5**
|
||||
*********
|
||||
|
||||
- **FIXED:** fixed a crash caused by having read-only Serializers nested by reference
|
||||
- **FIXED:** removed erroneous backslashes in paths when routes are generated using Django 2
|
||||
`path() <https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.path>`_
|
||||
- **IMPROVED:** updated ``swagger-ui`` to version 3.7.0
|
||||
- **IMPROVED:** ``FileField`` is now generated as an URL or file name in response Schemas
|
||||
(:pr:`21`, thanks to :ghuser:`h-hirokawa`)
|
||||
|
||||
*********
|
||||
**1.0.4**
|
||||
*********
|
||||
|
||||
- **FIXED:** fixed improper generation of YAML references
|
||||
- **ADDED:** added ``query_serializer`` parameter to
|
||||
:func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`)
|
||||
|
||||
*********
|
||||
**1.0.3**
|
||||
*********
|
||||
|
||||
- **FIXED:** fixed bug that caused schema views returned from cache to fail (:issue:`14`)
|
||||
- **FIXED:** 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
|
||||
+70
-1
@@ -4,9 +4,13 @@
|
||||
# drf-yasg documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Dec 10 15:20:34 2017.
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
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
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
@@ -165,6 +169,7 @@ nitpick_ignore = [
|
||||
|
||||
('py:class', 'ruamel.yaml.dumper.SafeDumper'),
|
||||
('py:class', 'rest_framework.renderers.BaseRenderer'),
|
||||
('py:class', 'rest_framework.schemas.generators.EndpointEnumerator'),
|
||||
('py:class', 'rest_framework.views.APIView'),
|
||||
|
||||
('py:class', 'OpenAPICodecYaml'),
|
||||
@@ -193,6 +198,11 @@ nitpick_ignore = [
|
||||
('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'))
|
||||
os.putenv('DJANGO_SETTINGS_MODULE', 'testproj.settings')
|
||||
|
||||
@@ -200,7 +210,66 @@ from django.conf import settings # noqa: E402
|
||||
|
||||
settings.configure()
|
||||
|
||||
# instantiate a SchemaView in the views module to make it available to autodoc
|
||||
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)
|
||||
|
||||
# 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/{}"
|
||||
gh_pr_uri = "https://github.com/axnsan12/drf-yasg/pull/{}"
|
||||
gh_user_uri = "https://github.com/{}"
|
||||
|
||||
|
||||
def sphinx_err(inliner, lineno, rawtext, msg):
|
||||
msg = inliner.reporter.error(msg, line=lineno)
|
||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||
return [prb], [msg]
|
||||
|
||||
|
||||
def sphinx_ref(options, rawtext, text, ref):
|
||||
set_classes(options)
|
||||
node = nodes.reference(rawtext, text, refuri=ref, **options)
|
||||
return [node], []
|
||||
|
||||
|
||||
def role_github_user(name, rawtext, text, lineno, inliner, options=None, content=None):
|
||||
options = options or {}
|
||||
content = content or []
|
||||
|
||||
if not re.match(r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$", text):
|
||||
return sphinx_err(inliner, lineno, rawtext, '"%s" is not a valid GitHub username.' % text)
|
||||
|
||||
ref = gh_user_uri.format(text)
|
||||
text = '@' + utils.unescape(text)
|
||||
return sphinx_ref(options, rawtext, text, ref)
|
||||
|
||||
|
||||
def role_github_pull_request_or_issue(name, rawtext, text, lineno, inliner, options=None, content=None):
|
||||
options = options or {}
|
||||
content = content or []
|
||||
try:
|
||||
if int(text) <= 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
return sphinx_err(
|
||||
inliner, lineno, rawtext,
|
||||
'GitHub pull request or issue number must be a number greater than or equal to 1; "%s" is invalid.' % text
|
||||
)
|
||||
|
||||
if name == 'pr':
|
||||
ref = gh_pr_uri
|
||||
elif name == 'issue':
|
||||
ref = gh_issue_uri
|
||||
else:
|
||||
return sphinx_err(inliner, lineno, rawtext, 'unknown role name for GitHub reference - "%s"' % name)
|
||||
|
||||
ref = ref.format(text)
|
||||
text = '#' + utils.unescape(text)
|
||||
return sphinx_ref(options, rawtext, text, ref)
|
||||
|
||||
|
||||
roles.register_local_role('pr', role_github_pull_request_or_issue)
|
||||
roles.register_local_role('issue', role_github_pull_request_or_issue)
|
||||
roles.register_local_role('ghuser', role_github_user)
|
||||
|
||||
+122
-3
@@ -50,9 +50,86 @@ is structured, starting from the root ``Swagger`` object.
|
||||
+ ``operationId`` - should be unique across all operations
|
||||
+ ``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
|
||||
Schemas, while Parameters are "primitives" and cannot contain other Parameters. The only exception are ``body``
|
||||
Parameters, which can contain a Schema.
|
||||
It is interesting to note the main differences between :class:`.Parameter` and :class:`.Schema` objects:
|
||||
|
||||
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||
| :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
|
||||
@@ -81,7 +158,9 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
||||
test_param = openapi.Parameter('test', openapi.IN_QUERY, description="test manual param", type=openapi.TYPE_BOOLEAN)
|
||||
user_response = openapi.Response('response description', UserSerializer)
|
||||
|
||||
# 'method' can be used to customize a single HTTP method of a view
|
||||
@swagger_auto_schema(method='get', manual_parameters=[test_param], responses={200: user_response})
|
||||
# 'methods' can be used to apply the same modification to multiple methods
|
||||
@swagger_auto_schema(methods=['put', 'post'], request_body=UserSerializer)
|
||||
@api_view(['GET', 'PUT', 'POST'])
|
||||
def user_detail(request, pk):
|
||||
@@ -110,6 +189,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
||||
.. code:: python
|
||||
|
||||
class ArticleViewSet(viewsets.ModelViewSet):
|
||||
# method or 'methods' can be skipped because the list_route only handles a single method (GET)
|
||||
@swagger_auto_schema(operation_description='GET /articles/today/')
|
||||
@list_route(methods=['get'])
|
||||
def today(self, request):
|
||||
@@ -129,6 +209,45 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
...
|
||||
|
||||
.. Tip::
|
||||
|
||||
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
|
||||
|
||||
@method_decorator(name='list', decorator=swagger_auto_schema(
|
||||
operation_description="description from swagger_auto_schema via method_decorator"
|
||||
))
|
||||
class ArticleViewSet(viewsets.ModelViewSet):
|
||||
...
|
||||
|
||||
This allows you to avoid unnecessarily overriding the method.
|
||||
|
||||
.. Tip::
|
||||
|
||||
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
|
||||
|
||||
decorated_login_view = \
|
||||
swagger_auto_schema(
|
||||
method='post',
|
||||
responses={status.HTTP_200_OK: LoginResponseSerializer}
|
||||
)(LoginView.as_view())
|
||||
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^login/$', decorated_login_view, name='login')
|
||||
]
|
||||
|
||||
This can allow you to avoid skipping an unnecessary *subclass* altogether.
|
||||
|
||||
.. Warning::
|
||||
|
||||
However, do note that both of the methods above can lead to unexpected (and maybe surprising) results by
|
||||
replacing/decorating methods on the base class itself.
|
||||
|
||||
*************************
|
||||
Subclassing and extending
|
||||
|
||||
@@ -57,6 +57,7 @@ drf\_yasg\.openapi
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:exclude-members: _bare_SwaggerDict
|
||||
|
||||
drf\_yasg\.renderers
|
||||
------------------------------
|
||||
|
||||
@@ -17,6 +17,7 @@ drf-yasg
|
||||
settings.rst
|
||||
contributing.rst
|
||||
license.rst
|
||||
changelog.rst
|
||||
|
||||
Source code documentation
|
||||
=========================
|
||||
|
||||
Generated
+368
-3
@@ -3,10 +3,375 @@
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
|
||||
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
|
||||
"requires": {
|
||||
"sprintf-js": "1.0.3"
|
||||
}
|
||||
},
|
||||
"autolinker": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz",
|
||||
"integrity": "sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI="
|
||||
},
|
||||
"builtin-status-codes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
||||
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
|
||||
},
|
||||
"call-me-maybe": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
|
||||
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms="
|
||||
},
|
||||
"clipboard": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
|
||||
"integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"good-listener": "1.2.2",
|
||||
"select": "1.1.2",
|
||||
"tiny-emitter": "2.0.2"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz",
|
||||
"integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==",
|
||||
"optional": true
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
|
||||
"integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"delegate": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
||||
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
||||
"optional": true
|
||||
},
|
||||
"dropkickjs": {
|
||||
"version": "2.1.10",
|
||||
"resolved": "https://registry.npmjs.org/dropkickjs/-/dropkickjs-2.1.10.tgz",
|
||||
"integrity": "sha1-8TyUAhQdoJ50rfTmN5jXkiBEOPI="
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz",
|
||||
"integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng=="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
|
||||
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
|
||||
},
|
||||
"foreach": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
|
||||
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
|
||||
},
|
||||
"format-util": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz",
|
||||
"integrity": "sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU="
|
||||
},
|
||||
"good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"delegate": "3.2.0"
|
||||
}
|
||||
},
|
||||
"hint.css": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/hint.css/-/hint.css-2.5.0.tgz",
|
||||
"integrity": "sha1-OMrjZn5C2R392+UDEAqzSTL2/WU="
|
||||
},
|
||||
"https-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
|
||||
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
|
||||
"requires": {
|
||||
"argparse": "1.0.9",
|
||||
"esprima": "4.0.0"
|
||||
}
|
||||
},
|
||||
"json-pointer": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.0.tgz",
|
||||
"integrity": "sha1-jlAFUKaqxUZKRzN32leqbMIoKNc=",
|
||||
"requires": {
|
||||
"foreach": "2.0.5"
|
||||
}
|
||||
},
|
||||
"json-schema-ref-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-stQTMhec2R/p2L9dH4XXRlpNCP0mY8QrLd/9Kl+8SHJQmwHtE1nDfXH4wbsSM+GkJMl8t92yZbI0OIol432CIQ==",
|
||||
"requires": {
|
||||
"call-me-maybe": "1.0.1",
|
||||
"debug": "3.1.0",
|
||||
"es6-promise": "4.1.1",
|
||||
"js-yaml": "3.10.0",
|
||||
"ono": "4.0.2",
|
||||
"z-schema": "3.19.0"
|
||||
}
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||
},
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
|
||||
},
|
||||
"lunr": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lunr/-/lunr-1.0.0.tgz",
|
||||
"integrity": "sha1-XJJ2ySyRrDWpJBtQGNRnI9kuL18="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"ono": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.2.tgz",
|
||||
"integrity": "sha512-EFXJFoeF+KkZW4lwmcPMKHp2ZU7o6CM+ccX2nPbEJKiJIdyqbIcS1v6pmNgeNJ6x4/vEYn0/8oz66qXSPnnmSQ==",
|
||||
"requires": {
|
||||
"format-util": "1.0.3"
|
||||
}
|
||||
},
|
||||
"openapi-sampler": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-0.4.3.tgz",
|
||||
"integrity": "sha512-Ml6o1gt++ZQ4JKL344YRo/fX05yuM6C+l/mGVX2yjhu1BRKyrRK4Z46uBTKSVaag1xINBFwYG7dZdz/10AmPzA=="
|
||||
},
|
||||
"perfect-scrollbar": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-0.8.1.tgz",
|
||||
"integrity": "sha512-RNC5tX/JMRYR+qVdJTEAWnRxw0Yf9lvbO8lTuAOvgDODkiA8lveTSkvrNMhmaGKEyimJpJl+myb/syVS9YyPuw=="
|
||||
},
|
||||
"prismjs": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.9.0.tgz",
|
||||
"integrity": "sha1-+j4tntw8OIfB8fMJXUHx+bQgDw8=",
|
||||
"requires": {
|
||||
"clipboard": "1.7.1"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"redoc": {
|
||||
"version": "1.19.3",
|
||||
"resolved": "https://registry.npmjs.org/redoc/-/redoc-1.19.3.tgz",
|
||||
"integrity": "sha1-DfPx+6S92G/+nGIAEzBxUjytVec=",
|
||||
"requires": {
|
||||
"core-js": "2.5.3",
|
||||
"dropkickjs": "2.1.10",
|
||||
"hint.css": "2.5.0",
|
||||
"https-browserify": "1.0.0",
|
||||
"json-pointer": "0.6.0",
|
||||
"json-schema-ref-parser": "3.3.1",
|
||||
"lunr": "1.0.0",
|
||||
"mark.js": "github:julmot/mark.js#714c9523feca999267f1758da8cfd92d077105d0",
|
||||
"openapi-sampler": "0.4.3",
|
||||
"perfect-scrollbar": "0.8.1",
|
||||
"prismjs": "1.9.0",
|
||||
"remarkable": "1.7.1",
|
||||
"scrollparent": "2.0.1",
|
||||
"slugify": "1.2.6",
|
||||
"stream-http": "2.7.2",
|
||||
"ts-helpers": "1.1.2",
|
||||
"zone.js": "0.8.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"mark.js": {
|
||||
"version": "github:julmot/mark.js#714c9523feca999267f1758da8cfd92d077105d0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"remarkable": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.1.tgz",
|
||||
"integrity": "sha1-qspJchALZqZCpjoQIcpLrBvjv/Y=",
|
||||
"requires": {
|
||||
"argparse": "0.1.16",
|
||||
"autolinker": "0.15.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "0.1.16",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz",
|
||||
"integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=",
|
||||
"requires": {
|
||||
"underscore": "1.7.0",
|
||||
"underscore.string": "2.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
||||
},
|
||||
"scrollparent": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.0.1.tgz",
|
||||
"integrity": "sha1-cV1bnMV3YPsivczDvvtb/gaxoxc="
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
|
||||
"optional": true
|
||||
},
|
||||
"slugify": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.2.6.tgz",
|
||||
"integrity": "sha512-796YAGnzEnLKQHAFf7H2q1nsjY/9qywSnF9ZkMUbs9he4aZaXO/zFUow0LZ95sBAiQjOX1EmGl23gTYaswiNaQ=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"stream-http": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz",
|
||||
"integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==",
|
||||
"requires": {
|
||||
"builtin-status-codes": "3.0.0",
|
||||
"inherits": "2.0.3",
|
||||
"readable-stream": "2.3.3",
|
||||
"to-arraybuffer": "1.0.1",
|
||||
"xtend": "4.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"swagger-ui-dist": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.6.1.tgz",
|
||||
"integrity": "sha1-uzQgV/h2COTs2DlGMDSJxjYicgY="
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.7.0.tgz",
|
||||
"integrity": "sha1-hkLAGUNf1SOE09KzVaHMovEDcoM="
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz",
|
||||
"integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==",
|
||||
"optional": true
|
||||
},
|
||||
"to-arraybuffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
|
||||
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M="
|
||||
},
|
||||
"ts-helpers": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-helpers/-/ts-helpers-1.1.2.tgz",
|
||||
"integrity": "sha1-/Gm+nx87rtAfsaDvjUz+dIgU2DU="
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
|
||||
"integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk="
|
||||
},
|
||||
"underscore.string": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz",
|
||||
"integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"validator": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.2.0.tgz",
|
||||
"integrity": "sha512-6Ij4Eo0KM4LkR0d0IegOwluG5453uqT5QyF5SV5Ezvm8/zmkKI/L4eoraafZGlZPC9guLkwKzgypcw8VGWWnGA=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
|
||||
},
|
||||
"z-schema": {
|
||||
"version": "3.19.0",
|
||||
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.19.0.tgz",
|
||||
"integrity": "sha512-V94f3ODuluBS4kQLLjNhwoMek0dyIXCsvNu/A17dAyJ6sMhT5KkJQwSn07R0naByLIXJWMDk+ruMfI/3G3hS4Q==",
|
||||
"requires": {
|
||||
"commander": "2.12.2",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"validator": "9.2.0"
|
||||
}
|
||||
},
|
||||
"zone.js": {
|
||||
"version": "0.8.18",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.18.tgz",
|
||||
"integrity": "sha512-knKOBQM0oea3/x9pdyDuDi7RhxDlJhOIkeixXSiTKWLgs4LpK37iBc+1HaHwzlciHUKT172CymJFKo8Xgh+44Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "drf-yasg",
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": "^3.6.1"
|
||||
"redoc": "^1.19.3",
|
||||
"swagger-ui-dist": "^3.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@ sphinx_rtd_theme==0.2.4
|
||||
Pillow==4.3.0
|
||||
readme_renderer==17.2
|
||||
|
||||
Django==1.11.8
|
||||
Django==2.0
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import io
|
||||
import os
|
||||
|
||||
import sys
|
||||
from setuptools import setup, find_packages
|
||||
import distutils.core
|
||||
|
||||
@@ -21,23 +22,24 @@ def _install_setup_requires(attrs):
|
||||
dist.fetch_build_eggs(dist.setup_requires)
|
||||
|
||||
|
||||
try:
|
||||
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
|
||||
# (setuptools_scm adds find_files hooks into setuptools on install)
|
||||
_install_setup_requires({'setup_requires': requirements_setup})
|
||||
except Exception:
|
||||
pass
|
||||
if 'sdist' in sys.argv:
|
||||
try:
|
||||
# try to install setuptools_scm before setuptools does it, otherwise our monkey patch below will come too early
|
||||
# (setuptools_scm adds find_files hooks into setuptools on install)
|
||||
_install_setup_requires({'setup_requires': requirements_setup})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
# see https://github.com/pypa/setuptools_scm/issues/190, setuptools_scm includes ALL versioned files from 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,
|
||||
# the setuptools_scm version should remain pinned to ensure it keeps working
|
||||
import setuptools_scm.integration
|
||||
try:
|
||||
# see https://github.com/pypa/setuptools_scm/issues/190, setuptools_scm includes ALL versioned files from
|
||||
# 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,
|
||||
# the setuptools_scm version should remain pinned to ensure it keeps working
|
||||
import setuptools_scm.integration
|
||||
|
||||
setuptools_scm.integration.find_files = lambda _: []
|
||||
except ImportError:
|
||||
pass
|
||||
setuptools_scm.integration.find_files = lambda _: []
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def read_req(req_file):
|
||||
|
||||
+22
-2
@@ -87,7 +87,7 @@ class _OpenAPICodec(object):
|
||||
:rtype: OrderedDict
|
||||
"""
|
||||
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
||||
return swagger
|
||||
return swagger.as_odict()
|
||||
|
||||
|
||||
class OpenAPICodecJson(_OpenAPICodec):
|
||||
@@ -101,6 +101,10 @@ class OpenAPICodecJson(_OpenAPICodec):
|
||||
class SaneYamlDumper(yaml.SafeDumper):
|
||||
"""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):
|
||||
"""https://stackoverflow.com/a/39681672
|
||||
|
||||
@@ -146,9 +150,25 @@ SaneYamlDumper.add_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):
|
||||
media_type = 'application/yaml'
|
||||
|
||||
def _dump_dict(self, spec):
|
||||
"""Dump ``spec`` into YAML."""
|
||||
return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8')
|
||||
return yaml_sane_dump(spec, binary=True)
|
||||
|
||||
@@ -1,21 +1,62 @@
|
||||
import re
|
||||
from collections import defaultdict, OrderedDict
|
||||
|
||||
import django.db.models
|
||||
import uritemplate
|
||||
from coreapi.compat import force_text
|
||||
from rest_framework.schemas.generators import SchemaGenerator
|
||||
from rest_framework.schemas.generators import SchemaGenerator, EndpointEnumerator as _EndpointEnumerator
|
||||
from rest_framework.schemas.inspectors import get_pk_description
|
||||
|
||||
from . import openapi
|
||||
from .inspectors import SwaggerAutoSchema
|
||||
from .openapi import ReferenceResolver
|
||||
|
||||
PATH_PARAMETER_RE = re.compile(r'{(?P<parameter>\w+)}')
|
||||
|
||||
|
||||
class EndpointEnumerator(_EndpointEnumerator):
|
||||
def get_path_from_regex(self, path_regex):
|
||||
return self.unescape_path(super(EndpointEnumerator, self).get_path_from_regex(path_regex))
|
||||
|
||||
def unescape(self, s):
|
||||
"""Unescape all backslash escapes from `s`.
|
||||
|
||||
:param str s: string with backslash escapes
|
||||
:rtype: str
|
||||
"""
|
||||
# unlike .replace('\\', ''), this corectly transforms a double backslash into a single backslash
|
||||
return re.sub(r'\\(.)', r'\1', s)
|
||||
|
||||
def unescape_path(self, path):
|
||||
"""Remove backslashes from all path components outside {parameters}. This is needed because
|
||||
Django>=2.0 ``path()``/``RoutePattern`` aggresively escapes all non-parameter path components.
|
||||
|
||||
**NOTE:** this might destructively affect some url regex patterns that contain metacharacters (e.g. \w, \d)
|
||||
outside path parameter groups; if you are in this category, God help you
|
||||
|
||||
:param str path: path possibly containing
|
||||
:return: the unescaped path
|
||||
:rtype: str
|
||||
"""
|
||||
clean_path = ''
|
||||
while path:
|
||||
match = PATH_PARAMETER_RE.search(path)
|
||||
if not match:
|
||||
clean_path += self.unescape(path)
|
||||
break
|
||||
clean_path += self.unescape(path[:match.start()])
|
||||
clean_path += match.group()
|
||||
path = path[match.end():]
|
||||
|
||||
return clean_path
|
||||
|
||||
|
||||
class OpenAPISchemaGenerator(object):
|
||||
"""
|
||||
This class iterates over all registered API endpoints and returns an appropriate OpenAPI 2.0 compliant schema.
|
||||
Method implementations shamelessly stolen and adapted from rest_framework SchemaGenerator.
|
||||
"""
|
||||
endpoint_enumerator_class = EndpointEnumerator
|
||||
|
||||
def __init__(self, info, version, url=None, patterns=None, urlconf=None):
|
||||
"""
|
||||
@@ -79,8 +120,8 @@ class OpenAPISchemaGenerator(object):
|
||||
:return: {path: (view_class, list[(http_method, view_instance)])
|
||||
:rtype: dict
|
||||
"""
|
||||
inspector = self._gen.endpoint_inspector_cls(self._gen.patterns, self._gen.urlconf)
|
||||
endpoints = inspector.get_api_endpoints()
|
||||
enumerator = self.endpoint_enumerator_class(self._gen.patterns, self._gen.urlconf)
|
||||
endpoints = enumerator.get_api_endpoints()
|
||||
|
||||
view_paths = defaultdict(list)
|
||||
view_cls = {}
|
||||
|
||||
+65
-21
@@ -10,7 +10,7 @@ from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from . import openapi
|
||||
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):
|
||||
@@ -30,6 +30,8 @@ def force_serializer_instance(serializer):
|
||||
|
||||
|
||||
class SwaggerAutoSchema(object):
|
||||
body_methods = ('PUT', 'PATCH', 'POST') #: methods allowed to have a request body
|
||||
|
||||
def __init__(self, view, path, method, overrides, components):
|
||||
"""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``
|
||||
: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()
|
||||
schema = None
|
||||
if serializer is None:
|
||||
@@ -109,6 +107,15 @@ class SwaggerAutoSchema(object):
|
||||
schema = self.get_request_body_schema(serializer)
|
||||
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):
|
||||
"""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 no_body:
|
||||
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):
|
||||
return body_override
|
||||
return force_serializer_instance(body_override)
|
||||
else:
|
||||
if not hasattr(self.view, 'get_serializer'):
|
||||
return None
|
||||
return self.view.get_serializer()
|
||||
elif self.method in self.body_methods:
|
||||
return self.get_view_serializer()
|
||||
|
||||
return None
|
||||
|
||||
def get_request_form_parameters(self, serializer):
|
||||
"""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`
|
||||
:rtype: list[openapi.Parameter]
|
||||
"""
|
||||
fields = getattr(serializer, 'fields', {})
|
||||
return [
|
||||
self.field_to_parameter(value, key, openapi.IN_FORM)
|
||||
for key, value
|
||||
in fields.items()
|
||||
]
|
||||
return self.serializer_to_parameters(serializer, in_=openapi.IN_FORM)
|
||||
|
||||
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.
|
||||
@@ -163,7 +168,7 @@ class SwaggerAutoSchema(object):
|
||||
:return: modified parameters
|
||||
: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 []
|
||||
|
||||
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; "
|
||||
"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())
|
||||
|
||||
def get_responses(self):
|
||||
@@ -218,13 +223,15 @@ class SwaggerAutoSchema(object):
|
||||
default_schema = ''
|
||||
if method == 'post':
|
||||
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':
|
||||
default_status = status.HTTP_204_NO_CONTENT
|
||||
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 ''
|
||||
if any(is_form_media_type(encoding) for encoding in self.get_consumes()):
|
||||
default_schema = ''
|
||||
if default_schema:
|
||||
if not isinstance(default_schema, openapi.Schema):
|
||||
default_schema = self.serializer_to_schema(default_schema)
|
||||
@@ -288,12 +295,35 @@ class SwaggerAutoSchema(object):
|
||||
|
||||
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):
|
||||
"""Return the query parameters accepted by this view.
|
||||
|
||||
: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):
|
||||
"""Determine whether filter backend parameters should be included for this request.
|
||||
@@ -398,12 +428,26 @@ class SwaggerAutoSchema(object):
|
||||
def serializer_to_schema(self, serializer):
|
||||
"""Convert a DRF Serializer instance to an :class:`.openapi.Schema`.
|
||||
|
||||
:param serializers.BaseSerializer serializer:
|
||||
:param serializers.BaseSerializer serializer: the ``Serializer`` instance
|
||||
:rtype: openapi.Schema
|
||||
"""
|
||||
definitions = self.components.with_scope(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_):
|
||||
"""Convert a DRF serializer Field to a swagger :class:`.Parameter` object.
|
||||
|
||||
|
||||
+41
-9
@@ -1,4 +1,3 @@
|
||||
import copy
|
||||
from collections import OrderedDict
|
||||
|
||||
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.
|
||||
|
||||
In particular,
|
||||
* if name starts with x\_, return "x-{camelCase}"
|
||||
* if name is 'ref', return "$ref"
|
||||
* if name starts with ``x_``, return ``x-{camelCase}``
|
||||
* if name is ``ref``, return ``$ref``
|
||||
* else return the name converted to camelCase, with trailing underscores stripped
|
||||
|
||||
: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)
|
||||
|
||||
|
||||
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):
|
||||
"""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):
|
||||
@@ -105,11 +114,34 @@ class SwaggerDict(OrderedDict):
|
||||
for attr, val in self._extras__.items():
|
||||
setattr(self, attr, val)
|
||||
|
||||
# noinspection PyArgumentList,PyDefaultArgument
|
||||
def __deepcopy__(self, memodict={}):
|
||||
result = OrderedDict(list(self.items()))
|
||||
result.update(copy.deepcopy(result, memodict))
|
||||
return result
|
||||
@staticmethod
|
||||
def _as_odict(obj, memo):
|
||||
"""Implementation detail of :meth:`.as_odict`"""
|
||||
if id(obj) in memo:
|
||||
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):
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+52
-12
@@ -4,6 +4,7 @@ from django.core.validators import RegexValidator
|
||||
from django.utils.encoding import force_text
|
||||
from rest_framework import serializers
|
||||
from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
from . import openapi
|
||||
from .errors import SwaggerGenerationError
|
||||
@@ -45,8 +46,8 @@ def is_list_view(path, method, view):
|
||||
return True
|
||||
|
||||
|
||||
def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, manual_parameters=None,
|
||||
operation_description=None, responses=None):
|
||||
def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, query_serializer=None,
|
||||
manual_parameters=None, operation_description=None, responses=None):
|
||||
"""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
|
||||
@@ -67,6 +68,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`
|
||||
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
|
||||
|
||||
:class:`.Parameter`\ s are identified by their (``name``, ``in``) combination, and any parameters given
|
||||
@@ -94,14 +104,16 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
||||
data = {
|
||||
'auto_schema': auto_schema,
|
||||
'request_body': request_body,
|
||||
'query_serializer': query_serializer,
|
||||
'manual_parameters': manual_parameters,
|
||||
'operation_description': operation_description,
|
||||
'responses': responses,
|
||||
}
|
||||
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', [])
|
||||
# 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)
|
||||
http_method_names = getattr(view_cls, 'http_method_names', [])
|
||||
if bind_to_methods or http_method_names:
|
||||
@@ -121,11 +133,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 " \
|
||||
"using one of the `method` or `methods` arguments" % _route
|
||||
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:
|
||||
_methods = [method.lower()]
|
||||
else:
|
||||
_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 all(mth in available_methods for mth in _methods), "method not bound to %s" % _route
|
||||
|
||||
@@ -134,7 +147,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod
|
||||
existing_data[available_methods[0]] = data
|
||||
view_method.swagger_auto_schema = existing_data
|
||||
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 " \
|
||||
"should also ensure that you put the swagger_auto_schema decorator AFTER (above) the _route decorator"
|
||||
view_method.swagger_auto_schema = data
|
||||
@@ -164,12 +177,15 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
||||
description = description if swagger_object_type != openapi.Items else None # Items has no description either
|
||||
|
||||
def SwaggerType(**instance_kwargs):
|
||||
if swagger_object_type == openapi.Parameter:
|
||||
if swagger_object_type == openapi.Parameter and 'required' not in instance_kwargs:
|
||||
instance_kwargs['required'] = field.required
|
||||
if swagger_object_type != openapi.Items:
|
||||
if swagger_object_type != openapi.Items and 'default' not in instance_kwargs:
|
||||
default = getattr(field, 'default', serializers.empty)
|
||||
if default is not serializers.empty:
|
||||
instance_kwargs['default'] = default
|
||||
if swagger_object_type == openapi.Schema and 'read_only' not in instance_kwargs:
|
||||
if field.read_only:
|
||||
instance_kwargs['read_only'] = True
|
||||
instance_kwargs.update(kwargs)
|
||||
return swagger_object_type(title=title, description=description, **instance_kwargs)
|
||||
|
||||
@@ -201,8 +217,6 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
||||
required = []
|
||||
for key, value in serializer.fields.items():
|
||||
properties[key] = serializer_field_to_swagger(value, ChildSwaggerType, definitions)
|
||||
if value.read_only:
|
||||
properties[key].read_only = value.read_only
|
||||
if value.required:
|
||||
required.append(key)
|
||||
|
||||
@@ -274,9 +288,20 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
|
||||
elif isinstance(field, serializers.FileField):
|
||||
# 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
|
||||
if swagger_object_type != openapi.Parameter:
|
||||
raise SwaggerGenerationError("parameter of type file is supported only in formData Parameter")
|
||||
return SwaggerType(type=openapi.TYPE_FILE)
|
||||
err = SwaggerGenerationError("FileField is supported only in a formData Parameter or response Schema")
|
||||
if swagger_object_type == openapi.Schema:
|
||||
# FileField.to_representation returns URL or file name
|
||||
result = SwaggerType(type=openapi.TYPE_STRING, read_only=True)
|
||||
if getattr(field, 'use_url', api_settings.UPLOADED_FILES_USE_URL):
|
||||
result.format = openapi.FORMAT_URI
|
||||
return result
|
||||
elif swagger_object_type == openapi.Parameter:
|
||||
param = SwaggerType(type=openapi.TYPE_FILE)
|
||||
if param['in'] != openapi.IN_FORM:
|
||||
raise err # pragma: no cover
|
||||
return param
|
||||
else:
|
||||
raise err # pragma: no cover
|
||||
elif isinstance(field, serializers.DictField) and swagger_object_type == openapi.Schema:
|
||||
child_schema = serializer_field_to_swagger(field.child, ChildSwaggerType, definitions)
|
||||
return SwaggerType(
|
||||
@@ -307,3 +332,18 @@ def find_regex(regex_field):
|
||||
|
||||
# regex_validator.regex should be a compiled re object...
|
||||
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
|
||||
|
||||
@@ -9,10 +9,12 @@ class ArticleSerializer(serializers.ModelSerializer):
|
||||
child=serializers.URLField(help_text="but i needed to test these 2 fields somehow"),
|
||||
)
|
||||
uuid = serializers.UUIDField(help_text="should articles have UUIDs?")
|
||||
cover_name = serializers.FileField(use_url=False, source='cover', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = ('title', 'body', 'slug', 'date_created', 'date_modified', 'references', 'uuid')
|
||||
fields = ('title', 'body', 'slug', 'date_created', 'date_modified',
|
||||
'references', 'uuid', 'cover', 'cover_name')
|
||||
read_only_fields = ('date_created', 'date_modified')
|
||||
lookup_field = 'slug'
|
||||
extra_kwargs = {'body': {'help_text': 'body serializer help_text'}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
|
||||
from django.utils.decorators import method_decorator
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import detail_route, list_route
|
||||
@@ -19,6 +20,9 @@ class NoPagingAutoSchema(SwaggerAutoSchema):
|
||||
return False
|
||||
|
||||
|
||||
@method_decorator(name='list', decorator=swagger_auto_schema(
|
||||
operation_description="description from swagger_auto_schema via method_decorator"
|
||||
))
|
||||
class ArticleViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ArticleViewSet class docstring
|
||||
@@ -54,7 +58,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
||||
return Response(serializer.data)
|
||||
|
||||
@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,))
|
||||
def image(self, request, slug=None):
|
||||
"""
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
from django.conf.urls import url
|
||||
import django
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'$', views.SnippetList.as_view()),
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
|
||||
]
|
||||
if django.VERSION[:2] >= (2, 0):
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.SnippetList.as_view()),
|
||||
path('<int:pk>/', views.SnippetDetail.as_view()),
|
||||
]
|
||||
else:
|
||||
from django.conf.urls import url
|
||||
urlpatterns = [
|
||||
url('^$', views.SnippetList.as_view()),
|
||||
url(r'^(?P<pk>\d+)/$', views.SnippetDetail.as_view()),
|
||||
]
|
||||
|
||||
@@ -29,7 +29,10 @@ def plain_view(request):
|
||||
urlpatterns = [
|
||||
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
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'^snippets/', include('snippets.urls')),
|
||||
|
||||
@@ -12,3 +12,8 @@ class UserSerializerrr(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
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!")
|
||||
|
||||
@@ -4,5 +4,5 @@ from users import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.UserList.as_view()),
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.user_detail),
|
||||
url(r'^(?P<pk>\d+)/$', views.user_detail),
|
||||
]
|
||||
|
||||
@@ -7,13 +7,13 @@ 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
|
||||
from users.serializers import UserSerializerrr, UserListQuerySerializer
|
||||
|
||||
|
||||
class UserList(APIView):
|
||||
"""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):
|
||||
queryset = User.objects.all()
|
||||
serializer = UserSerializerrr(queryset, many=True)
|
||||
|
||||
+2
-2
@@ -45,8 +45,8 @@ def validate_schema():
|
||||
from flex.core import parse as validate_flex
|
||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||
|
||||
validate_flex(swagger)
|
||||
validate_ssv(swagger)
|
||||
validate_flex(copy.deepcopy(swagger))
|
||||
validate_ssv(copy.deepcopy(swagger))
|
||||
|
||||
return validate_schema
|
||||
|
||||
|
||||
+29
-8
@@ -16,7 +16,7 @@ paths:
|
||||
/articles/:
|
||||
get:
|
||||
operationId: articles_list
|
||||
description: ArticleViewSet class docstring
|
||||
description: description from swagger_auto_schema via method_decorator
|
||||
parameters:
|
||||
- name: title
|
||||
in: query
|
||||
@@ -187,8 +187,6 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/Article'
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
tags:
|
||||
@@ -221,8 +219,8 @@ paths:
|
||||
required: true
|
||||
type: file
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
'201':
|
||||
description: ''
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
tags:
|
||||
@@ -353,7 +351,17 @@ paths:
|
||||
get:
|
||||
operationId: users_list
|
||||
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:
|
||||
'200':
|
||||
description: ''
|
||||
@@ -372,7 +380,7 @@ paths:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema: &id001
|
||||
schema:
|
||||
required:
|
||||
- username
|
||||
type: object
|
||||
@@ -382,7 +390,13 @@ paths:
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema: *id001
|
||||
schema:
|
||||
required:
|
||||
- username
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
consumes:
|
||||
- application/json
|
||||
tags:
|
||||
@@ -479,6 +493,13 @@ definitions:
|
||||
description: should articles have UUIDs?
|
||||
type: string
|
||||
format: uuid
|
||||
cover:
|
||||
type: string
|
||||
format: uri
|
||||
readOnly: true
|
||||
cover_name:
|
||||
type: string
|
||||
readOnly: true
|
||||
Project:
|
||||
required:
|
||||
- project_name
|
||||
|
||||
@@ -12,7 +12,7 @@ def test_appropriate_status_codes(swagger_dict):
|
||||
|
||||
def test_operation_docstrings(swagger_dict):
|
||||
articles_list = swagger_dict['paths']['/articles/']
|
||||
assert articles_list['get']['description'] == "ArticleViewSet class docstring"
|
||||
assert articles_list['get']['description'] == "description from swagger_auto_schema via method_decorator"
|
||||
assert articles_list['post']['description'] == "ArticleViewSet class docstring"
|
||||
|
||||
articles_detail = swagger_dict['paths']['/articles/{slug}/']
|
||||
|
||||
@@ -40,10 +40,18 @@ def test_json_codec_roundtrip(codec_json, generator, validate_schema):
|
||||
def test_yaml_codec_roundtrip(codec_yaml, generator, validate_schema):
|
||||
swagger = generator.get_schema(None, True)
|
||||
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')))
|
||||
|
||||
|
||||
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():
|
||||
generator = OpenAPISchemaGenerator(
|
||||
info=openapi.Info(title="Test generator", default_version="v1"),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
import pytest
|
||||
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)
|
||||
|
||||
|
||||
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')
|
||||
def test_non_public(client):
|
||||
response = client.get('/private/swagger.yaml')
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
npm update
|
||||
cp node_modules/redoc/dist/redoc.min.js src/drf_yasg/static/drf-yasg/redoc/redoc.min.js
|
||||
cp -r node_modules/swagger-ui-dist src/drf_yasg/static/drf-yasg/
|
||||
rm -f src/drf_yasg/static/drf-yasg/swagger-ui-dist/package.json src/drf_yasg/static/drf-yasg/swagger-ui-dist/.npmignore
|
||||
Reference in New Issue
Block a user