Merge pull request #20 from axnsan12/release/1.0.5

Version 1.0.5
openapi3 1.0.5
Cristi Vîjdea 2017-12-18 16:15:55 +01:00 committed by GitHub
commit 7683a28816
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 601 additions and 73 deletions

View File

@ -11,6 +11,7 @@ coverage:
default: default:
enabled: yes enabled: yes
target: auto target: auto
threshold: 0%
if_no_uploads: error if_no_uploads: error
if_ci_failed: error if_ci_failed: error
@ -18,7 +19,7 @@ coverage:
default: default:
enabled: yes enabled: yes
target: 80% target: 80%
threshold: 60% threshold: 0%
if_no_uploads: error if_no_uploads: error
if_ci_failed: error if_ci_failed: error

2
.gitattributes vendored 100644
View File

@ -0,0 +1,2 @@
* text=auto
*.sh text eol=lf

View File

@ -40,3 +40,8 @@ after_success:
branches: branches:
only: only:
- master - master
notifications:
email:
on_success: always
on_failure: always

View File

@ -359,7 +359,7 @@ https://drf-yasg.readthedocs.io/en/latest/
:alt: Codecov :alt: Codecov
.. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg .. |pypi-version| image:: https://img.shields.io/pypi/v/drf-yasg.svg
:target: https://pypi.org/project/drf-yasg/ :target: https://pypi.python.org/pypi/drf-yasg/
:alt: PyPI :alt: PyPI
.. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg .. |rtd-badge| image:: https://img.shields.io/readthedocs/drf-yasg.svg

View File

@ -3,20 +3,31 @@ 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** **1.0.4**
********* *********
- **FIX:** fixed improper generation of YAML references - **FIXED:** fixed improper generation of YAML references
- **FEATURE:** added ``query_serializer`` parameter to - **ADDED:** added ``query_serializer`` parameter to
:func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`) :func:`@swagger_auto_schema <.swagger_auto_schema>` (:issue:`16`, :pr:`17`)
********* *********
**1.0.3** **1.0.3**
********* *********
- **FIX:** fixed bug that caused schema views returned from cache to fail (:issue:`14`) - **FIXED:** 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 - **FIXED:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by
attempting to shove file parameters into Schema objects attempting to shove file parameters into Schema objects
********* *********

View File

@ -4,6 +4,7 @@
# drf-yasg documentation build configuration file, created by # drf-yasg documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 10 15:20:34 2017. # sphinx-quickstart on Sun Dec 10 15:20:34 2017.
import os import os
import re
import sys import sys
import sphinx_rtd_theme import sphinx_rtd_theme
@ -168,6 +169,7 @@ nitpick_ignore = [
('py:class', 'ruamel.yaml.dumper.SafeDumper'), ('py:class', 'ruamel.yaml.dumper.SafeDumper'),
('py:class', 'rest_framework.renderers.BaseRenderer'), ('py:class', 'rest_framework.renderers.BaseRenderer'),
('py:class', 'rest_framework.schemas.generators.EndpointEnumerator'),
('py:class', 'rest_framework.views.APIView'), ('py:class', 'rest_framework.views.APIView'),
('py:class', 'OpenAPICodecYaml'), ('py:class', 'OpenAPICodecYaml'),
@ -215,40 +217,59 @@ drf_yasg.views.SchemaView = drf_yasg.views.get_schema_view(None)
# custom interpreted role for linking to GitHub issues and pull requests # custom interpreted role for linking to GitHub issues and pull requests
# use as :issue:`14` or :pr:`17` # use as :issue:`14` or :pr:`17`
gh_issue_uri = "https://github.com/axnsan12/drf-yasg/issues/%d" gh_issue_uri = "https://github.com/axnsan12/drf-yasg/issues/{}"
gh_pr_uri = "https://github.com/axnsan12/drf-yasg/pull/%d" 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): def role_github_pull_request_or_issue(name, rawtext, text, lineno, inliner, options=None, content=None):
options = options or {} options = options or {}
content = content or [] content = content or []
try: try:
ghid = int(text) if int(text) <= 0:
if ghid <= 0:
raise ValueError raise ValueError
except ValueError: except ValueError:
msg = inliner.reporter.error( return sphinx_err(
'GitHub pull request or issue number must be a number greater than or equal to 1; ' inliner, lineno, rawtext,
'"%s" is invalid.' % text, line=lineno 'GitHub pull request or issue number must be a number greater than or equal to 1; "%s" is invalid.' % text
) )
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
# Base URL mainly used by inliner.rfc_reference, so this is correct:
if name == 'pr': if name == 'pr':
ref = gh_pr_uri ref = gh_pr_uri
elif name == 'issue': elif name == 'issue':
ref = gh_issue_uri ref = gh_issue_uri
else: else:
msg = inliner.reporter.error('unknown tag name for GitHub reference - "%s"' % name, line=lineno) return sphinx_err(inliner, lineno, rawtext, 'unknown role name for GitHub reference - "%s"' % name)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
ref = ref % ghid ref = ref.format(text)
set_classes(options) text = '#' + utils.unescape(text)
node = nodes.reference(rawtext, '#' + utils.unescape(text), refuri=ref, **options) return sphinx_ref(options, rawtext, text, ref)
return [node], []
roles.register_local_role('pr', role_github_pull_request_or_issue) 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('issue', role_github_pull_request_or_issue)
roles.register_local_role('ghuser', role_github_user)

View File

@ -158,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) test_param = openapi.Parameter('test', openapi.IN_QUERY, description="test manual param", type=openapi.TYPE_BOOLEAN)
user_response = openapi.Response('response description', UserSerializer) 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}) @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) @swagger_auto_schema(methods=['put', 'post'], request_body=UserSerializer)
@api_view(['GET', 'PUT', 'POST']) @api_view(['GET', 'PUT', 'POST'])
def user_detail(request, pk): def user_detail(request, pk):
@ -187,6 +189,7 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
.. code:: python .. code:: python
class ArticleViewSet(viewsets.ModelViewSet): 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/') @swagger_auto_schema(operation_description='GET /articles/today/')
@list_route(methods=['get']) @list_route(methods=['get'])
def today(self, request): def today(self, request):
@ -206,6 +209,45 @@ Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decora
def partial_update(self, request, *args, **kwargs): 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 Subclassing and extending

371
package-lock.json generated
View File

@ -3,10 +3,375 @@
"requires": true, "requires": true,
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "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": { "swagger-ui-dist": {
"version": "3.6.1", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.6.1.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.7.0.tgz",
"integrity": "sha1-uzQgV/h2COTs2DlGMDSJxjYicgY=" "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=="
} }
} }
} }

View File

@ -1,6 +1,7 @@
{ {
"name": "drf-yasg", "name": "drf-yasg",
"dependencies": { "dependencies": {
"swagger-ui-dist": "^3.6.1" "redoc": "^1.19.3",
"swagger-ui-dist": "^3.7.0"
} }
} }

View File

@ -3,4 +3,4 @@ sphinx_rtd_theme==0.2.4
Pillow==4.3.0 Pillow==4.3.0
readme_renderer==17.2 readme_renderer==17.2
Django==1.11.8 Django==2.0

View File

@ -1,21 +1,62 @@
import re
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
import django.db.models import django.db.models
import uritemplate import uritemplate
from coreapi.compat import force_text 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 rest_framework.schemas.inspectors import get_pk_description
from . import openapi from . import openapi
from .inspectors import SwaggerAutoSchema from .inspectors import SwaggerAutoSchema
from .openapi import ReferenceResolver 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): class OpenAPISchemaGenerator(object):
""" """
This class iterates over all registered API endpoints and returns an appropriate OpenAPI 2.0 compliant schema. 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. 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): 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)]) :return: {path: (view_class, list[(http_method, view_instance)])
:rtype: dict :rtype: dict
""" """
inspector = self._gen.endpoint_inspector_cls(self._gen.patterns, self._gen.urlconf) enumerator = self.endpoint_enumerator_class(self._gen.patterns, self._gen.urlconf)
endpoints = inspector.get_api_endpoints() endpoints = enumerator.get_api_endpoints()
view_paths = defaultdict(list) view_paths = defaultdict(list)
view_cls = {} view_cls = {}

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

View File

@ -4,6 +4,7 @@ from django.core.validators import RegexValidator
from django.utils.encoding import force_text from django.utils.encoding import force_text
from rest_framework import serializers from rest_framework import serializers
from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin
from rest_framework.settings import api_settings
from . import openapi from . import openapi
from .errors import SwaggerGenerationError from .errors import SwaggerGenerationError
@ -176,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 description = description if swagger_object_type != openapi.Items else None # Items has no description either
def SwaggerType(**instance_kwargs): 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 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) default = getattr(field, 'default', serializers.empty)
if default is not serializers.empty: if default is not serializers.empty:
instance_kwargs['default'] = default 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) instance_kwargs.update(kwargs)
return swagger_object_type(title=title, description=description, **instance_kwargs) return swagger_object_type(title=title, description=description, **instance_kwargs)
@ -213,8 +217,6 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
required = [] required = []
for key, value in serializer.fields.items(): for key, value in serializer.fields.items():
properties[key] = serializer_field_to_swagger(value, ChildSwaggerType, definitions) properties[key] = serializer_field_to_swagger(value, ChildSwaggerType, definitions)
if value.read_only:
properties[key].read_only = value.read_only
if value.required: if value.required:
required.append(key) required.append(key)
@ -286,13 +288,20 @@ 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") err = SwaggerGenerationError("FileField is supported only in a formData Parameter or response Schema")
if swagger_object_type != openapi.Parameter: if swagger_object_type == openapi.Schema:
raise err # pragma: no cover # 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) param = SwaggerType(type=openapi.TYPE_FILE)
if param['in'] != openapi.IN_FORM: if param['in'] != openapi.IN_FORM:
raise err # pragma: no cover raise err # pragma: no cover
return param return param
else:
raise err # pragma: no cover
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(

View File

@ -9,10 +9,12 @@ class ArticleSerializer(serializers.ModelSerializer):
child=serializers.URLField(help_text="but i needed to test these 2 fields somehow"), child=serializers.URLField(help_text="but i needed to test these 2 fields somehow"),
) )
uuid = serializers.UUIDField(help_text="should articles have UUIDs?") uuid = serializers.UUIDField(help_text="should articles have UUIDs?")
cover_name = serializers.FileField(use_url=False, source='cover', read_only=True)
class Meta: class Meta:
model = Article 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') read_only_fields = ('date_created', 'date_modified')
lookup_field = 'slug' lookup_field = 'slug'
extra_kwargs = {'body': {'help_text': 'body serializer help_text'}} extra_kwargs = {'body': {'help_text': 'body serializer help_text'}}

View File

@ -1,5 +1,6 @@
import datetime import datetime
from django.utils.decorators import method_decorator
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
@ -19,6 +20,9 @@ class NoPagingAutoSchema(SwaggerAutoSchema):
return False return False
@method_decorator(name='list', decorator=swagger_auto_schema(
operation_description="description from swagger_auto_schema via method_decorator"
))
class ArticleViewSet(viewsets.ModelViewSet): class ArticleViewSet(viewsets.ModelViewSet):
""" """
ArticleViewSet class docstring ArticleViewSet class docstring

View File

@ -1,8 +1,17 @@
from django.conf.urls import url import django
from . import views from . import views
if django.VERSION[:2] >= (2, 0):
from django.urls import path
urlpatterns = [ urlpatterns = [
url(r'$', views.SnippetList.as_view()), path('', views.SnippetList.as_view()),
url(r'^(?P<pk>[0-9]+)/$', views.SnippetDetail.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()),
] ]

View File

@ -4,5 +4,5 @@ from users import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.UserList.as_view()), url(r'^$', views.UserList.as_view()),
url(r'^(?P<pk>[0-9]+)/$', views.user_detail), url(r'^(?P<pk>\d+)/$', views.user_detail),
] ]

View File

@ -16,7 +16,7 @@ paths:
/articles/: /articles/:
get: get:
operationId: articles_list operationId: articles_list
description: ArticleViewSet class docstring description: description from swagger_auto_schema via method_decorator
parameters: parameters:
- name: title - name: title
in: query in: query
@ -493,6 +493,13 @@ definitions:
description: should articles have UUIDs? description: should articles have UUIDs?
type: string type: string
format: uuid format: uuid
cover:
type: string
format: uri
readOnly: true
cover_name:
type: string
readOnly: true
Project: Project:
required: required:
- project_name - project_name

View File

@ -12,7 +12,7 @@ def test_appropriate_status_codes(swagger_dict):
def test_operation_docstrings(swagger_dict): def test_operation_docstrings(swagger_dict):
articles_list = swagger_dict['paths']['/articles/'] 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" assert articles_list['post']['description'] == "ArticleViewSet class docstring"
articles_detail = swagger_dict['paths']['/articles/{slug}/'] articles_detail = swagger_dict['paths']['/articles/{slug}/']

7
update-ui.sh 100644
View File

@ -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