diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index d6fdc86..0aad453 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -54,7 +54,6 @@
-
diff --git a/docs/drf_yasg.rst b/docs/drf_yasg.rst
index dc8c25d..9fa9a4b 100644
--- a/docs/drf_yasg.rst
+++ b/docs/drf_yasg.rst
@@ -57,6 +57,7 @@ drf\_yasg\.openapi
:members:
:undoc-members:
:show-inheritance:
+ :exclude-members: _bare_SwaggerDict
drf\_yasg\.renderers
------------------------------
diff --git a/src/drf_yasg/codecs.py b/src/drf_yasg/codecs.py
index 784c3c9..2e4f460 100644
--- a/src/drf_yasg/codecs.py
+++ b/src/drf_yasg/codecs.py
@@ -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):
@@ -146,9 +146,24 @@ 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
+
+ :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)
diff --git a/src/drf_yasg/openapi.py b/src/drf_yasg/openapi.py
index e434ee7..0e8e662 100644
--- a/src/drf_yasg/openapi.py
+++ b/src/drf_yasg/openapi.py
@@ -62,6 +62,13 @@ 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`. Attribute names starting with ``_`` are set on the object as-is and are not included
@@ -108,11 +115,25 @@ 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):
+ if isinstance(obj, dict):
+ result = OrderedDict()
+ for attr, val in obj.items():
+ result[attr] = SwaggerDict._as_odict(val)
+ 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):
diff --git a/testproj/testproj/urls.py b/testproj/testproj/urls.py
index 6c2136e..d1ea6a4 100644
--- a/testproj/testproj/urls.py
+++ b/testproj/testproj/urls.py
@@ -29,7 +29,10 @@ def plain_view(request):
urlpatterns = [
url(r'^swagger(?P.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.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')),
diff --git a/tests/conftest.py b/tests/conftest.py
index 008669a..d4efdb1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -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
diff --git a/tests/test_schema_views.py b/tests/test_schema_views.py
index bb38cad..aabd828 100644
--- a/tests/test_schema_views.py
+++ b/tests/test_schema_views.py
@@ -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')