Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bdf7e8a4ae | |||
| 174f1153b4 | |||
| af2a44e1e9 | |||
| f6a535eb45 |
-1
@@ -54,7 +54,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PyShadowingNamesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="PyShadowingNamesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PyUnusedLocalInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
<inspection_tool class="PyUnusedLocalInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||||
<option name="ignoreTupleUnpacking" value="true" />
|
<option name="ignoreTupleUnpacking" value="true" />
|
||||||
<option name="ignoreLambdaParameters" value="true" />
|
<option name="ignoreLambdaParameters" value="true" />
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#########
|
||||||
|
Changelog
|
||||||
|
#########
|
||||||
|
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.0.3**
|
||||||
|
*********
|
||||||
|
|
||||||
|
- **FIX:** fixed bug that caused schema views returned from cache to fail (:issue:`14`)
|
||||||
|
- **FIX:** disabled automatic generation of response schemas for form operations to avoid confusing errors caused by
|
||||||
|
attempting to shove file parameters into Schema objects
|
||||||
|
|
||||||
|
*********
|
||||||
|
**1.0.2**
|
||||||
|
*********
|
||||||
|
|
||||||
|
- First published version
|
||||||
@@ -7,6 +7,9 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import sphinx_rtd_theme
|
import sphinx_rtd_theme
|
||||||
|
from docutils import nodes, utils
|
||||||
|
from docutils.parsers.rst import roles
|
||||||
|
from docutils.parsers.rst.roles import set_classes
|
||||||
from pkg_resources import get_distribution
|
from pkg_resources import get_distribution
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
@@ -204,3 +207,41 @@ import drf_yasg.views # noqa: E402
|
|||||||
|
|
||||||
# instantiate a SchemaView in the views module to make it available to autodoc
|
# instantiate a SchemaView in the views module to make it available to autodoc
|
||||||
drf_yasg.views.SchemaView = drf_yasg.views.get_schema_view(None)
|
drf_yasg.views.SchemaView = drf_yasg.views.get_schema_view(None)
|
||||||
|
|
||||||
|
ghiss_uri = "https://github.com/axnsan12/drf-yasg/issues/%d"
|
||||||
|
ghpr_uri = "https://github.com/axnsan12/drf-yasg/pull/%d"
|
||||||
|
|
||||||
|
|
||||||
|
def role_github_pull_request_or_issue(name, rawtext, text, lineno, inliner, options=None, content=None):
|
||||||
|
options = options or {}
|
||||||
|
content = content or []
|
||||||
|
try:
|
||||||
|
ghid = int(text)
|
||||||
|
if ghid <= 0:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
msg = inliner.reporter.error(
|
||||||
|
'GitHub pull request or issue number must be a number greater than or equal to 1; '
|
||||||
|
'"%s" is invalid.' % text, line=lineno
|
||||||
|
)
|
||||||
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
|
return [prb], [msg]
|
||||||
|
# Base URL mainly used by inliner.rfc_reference, so this is correct:
|
||||||
|
|
||||||
|
if name == 'pr':
|
||||||
|
ref = ghpr_uri
|
||||||
|
elif name == 'issue':
|
||||||
|
ref = ghiss_uri
|
||||||
|
else:
|
||||||
|
msg = inliner.reporter.error('unknown tag name for GitHub reference - "%s"' % name, line=lineno)
|
||||||
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
|
return [prb], [msg]
|
||||||
|
|
||||||
|
ref = ref % ghid
|
||||||
|
set_classes(options)
|
||||||
|
node = nodes.reference(rawtext, '#' + utils.unescape(text), refuri=ref, **options)
|
||||||
|
return [node], []
|
||||||
|
|
||||||
|
|
||||||
|
roles.register_local_role('pr', role_github_pull_request_or_issue)
|
||||||
|
roles.register_local_role('issue', role_github_pull_request_or_issue)
|
||||||
|
|||||||
+21
-3
@@ -50,9 +50,27 @@ is structured, starting from the root ``Swagger`` object.
|
|||||||
+ ``operationId`` - should be unique across all operations
|
+ ``operationId`` - should be unique across all operations
|
||||||
+ ``tags`` - used to group operations in the listing
|
+ ``tags`` - used to group operations in the listing
|
||||||
|
|
||||||
It is interesting to note that the main difference between ``Parameter`` and ``Schema`` is that Schemas can nest other
|
It is interesting to note the main differences between :class:`.Parameter` and :class:`.Schema` objects:
|
||||||
Schemas, while Parameters are "primitives" and cannot contain other Parameters. The only exception are ``body``
|
|
||||||
Parameters, which can contain a Schema.
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| :class:`.Schema` | :class:`.Parameter` |
|
||||||
|
+==========================================================+===========================================================+
|
||||||
|
| Can nest other Schemas | Cannot nest other Parameters |br| |
|
||||||
|
| | Can only nest a Schema if the parameter is ``in: body`` |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Cannot describe file uploads |br| | Can describe file uploads via ``type`` = ``file``, |br| |
|
||||||
|
| - ``file`` is not permitted as a value for ``type`` | but only as part of a form :class:`.Operation` [#formop]_ |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Can be used in :class:`.Response`\ s | Cannot be used in :class:`.Response`\ s |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
| Cannot be used in form :class:`.Operation`\ s [#formop]_ | Can be used in form :class:`.Operation`\ s [#formop]_ |
|
||||||
|
+----------------------------------------------------------+-----------------------------------------------------------+
|
||||||
|
|
||||||
|
.. [#formop] a form Operation is an :class:`.Operation` that consumes ``multipart/form-data`` or
|
||||||
|
``application/x-www-form-urlencoded``
|
||||||
|
|
||||||
|
* a form Operation cannot have ``body`` parameters
|
||||||
|
* a non-form operation cannot have ``form`` parameters
|
||||||
|
|
||||||
**************************************
|
**************************************
|
||||||
The ``@swagger_auto_schema`` decorator
|
The ``@swagger_auto_schema`` decorator
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ drf\_yasg\.openapi
|
|||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
:exclude-members: _bare_SwaggerDict
|
||||||
|
|
||||||
drf\_yasg\.renderers
|
drf\_yasg\.renderers
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ drf-yasg
|
|||||||
settings.rst
|
settings.rst
|
||||||
contributing.rst
|
contributing.rst
|
||||||
license.rst
|
license.rst
|
||||||
|
changelog.rst
|
||||||
|
|
||||||
Source code documentation
|
Source code documentation
|
||||||
=========================
|
=========================
|
||||||
|
|||||||
+17
-2
@@ -87,7 +87,7 @@ class _OpenAPICodec(object):
|
|||||||
:rtype: OrderedDict
|
:rtype: OrderedDict
|
||||||
"""
|
"""
|
||||||
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS
|
||||||
return swagger
|
return swagger.as_odict()
|
||||||
|
|
||||||
|
|
||||||
class OpenAPICodecJson(_OpenAPICodec):
|
class OpenAPICodecJson(_OpenAPICodec):
|
||||||
@@ -146,9 +146,24 @@ SaneYamlDumper.add_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
|||||||
SaneYamlDumper.add_multi_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
SaneYamlDumper.add_multi_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_sane_dump(data, binary):
|
||||||
|
"""Dump the given data dictionary into a sane format:
|
||||||
|
|
||||||
|
* OrderedDicts are dumped as regular mappings instead of non-standard !!odict
|
||||||
|
* multi-line mapping style instead of json-like inline style
|
||||||
|
* list elements are indented into their parents
|
||||||
|
|
||||||
|
:param dict data: the data to be serializers
|
||||||
|
:param bool binary: True to return a utf-8 encoded binary object, False to return a string
|
||||||
|
:return: the serialized YAML
|
||||||
|
:rtype: str,bytes
|
||||||
|
"""
|
||||||
|
return yaml.dump(data, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8' if binary else None)
|
||||||
|
|
||||||
|
|
||||||
class OpenAPICodecYaml(_OpenAPICodec):
|
class OpenAPICodecYaml(_OpenAPICodec):
|
||||||
media_type = 'application/yaml'
|
media_type = 'application/yaml'
|
||||||
|
|
||||||
def _dump_dict(self, spec):
|
def _dump_dict(self, spec):
|
||||||
"""Dump ``spec`` into YAML."""
|
"""Dump ``spec`` into YAML."""
|
||||||
return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8')
|
return yaml_sane_dump(spec, binary=True)
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ class SwaggerAutoSchema(object):
|
|||||||
default_schema = self.get_request_serializer()
|
default_schema = self.get_request_serializer()
|
||||||
|
|
||||||
default_schema = default_schema or ''
|
default_schema = default_schema or ''
|
||||||
|
if any(is_form_media_type(encoding) for encoding in self.get_consumes()):
|
||||||
|
default_schema = ''
|
||||||
if default_schema:
|
if default_schema:
|
||||||
if not isinstance(default_schema, openapi.Schema):
|
if not isinstance(default_schema, openapi.Schema):
|
||||||
default_schema = self.serializer_to_schema(default_schema)
|
default_schema = self.serializer_to_schema(default_schema)
|
||||||
|
|||||||
+31
-8
@@ -1,4 +1,3 @@
|
|||||||
import copy
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from coreapi.compat import urlparse
|
from coreapi.compat import urlparse
|
||||||
@@ -48,8 +47,8 @@ def make_swagger_name(attribute_name):
|
|||||||
Convert a python variable name into a Swagger spec attribute name.
|
Convert a python variable name into a Swagger spec attribute name.
|
||||||
|
|
||||||
In particular,
|
In particular,
|
||||||
* if name starts with x\_, return "x-{camelCase}"
|
* if name starts with ``x_``, return ``x-{camelCase}``
|
||||||
* if name is 'ref', return "$ref"
|
* if name is ``ref``, return ``$ref``
|
||||||
* else return the name converted to camelCase, with trailing underscores stripped
|
* else return the name converted to camelCase, with trailing underscores stripped
|
||||||
|
|
||||||
:param str attribute_name: python attribute name
|
:param str attribute_name: python attribute name
|
||||||
@@ -62,9 +61,19 @@ def make_swagger_name(attribute_name):
|
|||||||
return camelize(attribute_name.rstrip('_'), uppercase_first_letter=False)
|
return camelize(attribute_name.rstrip('_'), uppercase_first_letter=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _bare_SwaggerDict(cls):
|
||||||
|
assert issubclass(cls, SwaggerDict)
|
||||||
|
result = cls.__new__(cls)
|
||||||
|
OrderedDict.__init__(result) # no __init__ called for SwaggerDict subclasses!
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class SwaggerDict(OrderedDict):
|
class SwaggerDict(OrderedDict):
|
||||||
"""A particular type of OrderedDict, which maps all attribute accesses to dict lookups using
|
"""A particular type of OrderedDict, which maps all attribute accesses to dict lookups using
|
||||||
:func:`.make_swagger_name`. Used as a base class for all Swagger helper models.
|
:func:`.make_swagger_name`. Attribute names starting with ``_`` are set on the object as-is and are not included
|
||||||
|
in the specification output.
|
||||||
|
|
||||||
|
Used as a base class for all Swagger helper models.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **attrs):
|
def __init__(self, **attrs):
|
||||||
@@ -105,11 +114,25 @@ class SwaggerDict(OrderedDict):
|
|||||||
for attr, val in self._extras__.items():
|
for attr, val in self._extras__.items():
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
|
||||||
# noinspection PyArgumentList,PyDefaultArgument
|
@staticmethod
|
||||||
def __deepcopy__(self, memodict={}):
|
def _as_odict(obj):
|
||||||
result = OrderedDict(list(self.items()))
|
if isinstance(obj, dict):
|
||||||
result.update(copy.deepcopy(result, memodict))
|
result = OrderedDict()
|
||||||
|
for attr, val in obj.items():
|
||||||
|
result[attr] = SwaggerDict._as_odict(val)
|
||||||
return result
|
return result
|
||||||
|
elif isinstance(obj, (list, tuple)):
|
||||||
|
return type(obj)(SwaggerDict._as_odict(elem) for elem in obj)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def as_odict(self):
|
||||||
|
return SwaggerDict._as_odict(self)
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
# for pickle supprt; this skips calls to all SwaggerDict __init__ methods and relies
|
||||||
|
# on the already set attributes instead
|
||||||
|
return _bare_SwaggerDict, (type(self),), vars(self), None, iter(self.items())
|
||||||
|
|
||||||
|
|
||||||
class Contact(SwaggerDict):
|
class Contact(SwaggerDict):
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
|
|||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(method='get', operation_description="image GET description override")
|
@swagger_auto_schema(method='get', operation_description="image GET description override")
|
||||||
@swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer, responses={200: 'success'})
|
@swagger_auto_schema(method='post', request_body=serializers.ImageUploadSerializer)
|
||||||
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))
|
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))
|
||||||
def image(self, request, slug=None):
|
def image(self, request, slug=None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ def plain_view(request):
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
|
url(r'^cached/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
||||||
|
url(r'^cached/swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
|
||||||
|
url(r'^cached/redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
||||||
|
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^snippets/', include('snippets.urls')),
|
url(r'^snippets/', include('snippets.urls')),
|
||||||
|
|||||||
+2
-2
@@ -45,8 +45,8 @@ def validate_schema():
|
|||||||
from flex.core import parse as validate_flex
|
from flex.core import parse as validate_flex
|
||||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||||
|
|
||||||
validate_flex(swagger)
|
validate_flex(copy.deepcopy(swagger))
|
||||||
validate_ssv(swagger)
|
validate_ssv(copy.deepcopy(swagger))
|
||||||
|
|
||||||
return validate_schema
|
return validate_schema
|
||||||
|
|
||||||
|
|||||||
@@ -187,8 +187,6 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/Article'
|
|
||||||
consumes:
|
consumes:
|
||||||
- multipart/form-data
|
- multipart/form-data
|
||||||
tags:
|
tags:
|
||||||
@@ -221,8 +219,8 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
type: file
|
type: file
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'201':
|
||||||
description: success
|
description: ''
|
||||||
consumes:
|
consumes:
|
||||||
- multipart/form-data
|
- multipart/form-data
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from ruamel import yaml
|
from ruamel import yaml
|
||||||
@@ -46,6 +47,26 @@ def test_redoc(client, validate_schema):
|
|||||||
_validate_text_schema_view(client, validate_schema, '/redoc/?format=openapi', json.loads)
|
_validate_text_schema_view(client, validate_schema, '/redoc/?format=openapi', json.loads)
|
||||||
|
|
||||||
|
|
||||||
|
def test_caching(client, validate_schema):
|
||||||
|
prev_schema = None
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
_validate_ui_schema_view(client, '/cached/redoc/', 'redoc/redoc.min.js')
|
||||||
|
_validate_text_schema_view(client, validate_schema, '/cached/redoc/?format=openapi', json.loads)
|
||||||
|
_validate_ui_schema_view(client, '/cached/swagger/', 'swagger-ui-dist/swagger-ui-bundle.js')
|
||||||
|
_validate_text_schema_view(client, validate_schema, '/cached/swagger/?format=openapi', json.loads)
|
||||||
|
|
||||||
|
json_schema = client.get('/cached/swagger.json')
|
||||||
|
assert json_schema.status_code == 200
|
||||||
|
json_schema = json.loads(json_schema.content.decode('utf-8'), object_pairs_hook=OrderedDict)
|
||||||
|
if prev_schema is None:
|
||||||
|
validate_schema(json_schema)
|
||||||
|
prev_schema = json_schema
|
||||||
|
else:
|
||||||
|
from datadiff.tools import assert_equal
|
||||||
|
assert_equal(prev_schema, json_schema)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.urls('urlconfs.non_public_urls')
|
@pytest.mark.urls('urlconfs.non_public_urls')
|
||||||
def test_non_public(client):
|
def test_non_public(client):
|
||||||
response = client.get('/private/swagger.yaml')
|
response = client.get('/private/swagger.yaml')
|
||||||
|
|||||||
Reference in New Issue
Block a user