From 6ea8711a1f5a291f94d5b0ce1d40d6d45596a8b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=20V=C3=AEjdea?= Date: Mon, 5 Mar 2018 11:51:51 +0200 Subject: [PATCH] Fix in-place modification of swagger_auto_schema arguments (#75) Fixes #74 --- docs/changelog.rst | 9 +++++++++ src/drf_yasg/generators.py | 3 ++- src/drf_yasg/utils.py | 13 +++++++++---- tests/test_schema_generator.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a0fdf65..9842864 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,15 @@ Changelog ######### +********* +**1.4.5** +********* + +*Release date: Mar 05, 2018* + +- **FIXED:** fixed an issue with modification of ``swagger_auto_schema`` arguments in-place during introspection, which + would sometimes cause an incomplete Swagger document to be generated after the first pass (:issue:`74`, :pr:`75`) + ********* **1.4.4** ********* diff --git a/src/drf_yasg/generators.py b/src/drf_yasg/generators.py index 84db625..64a74b7 100644 --- a/src/drf_yasg/generators.py +++ b/src/drf_yasg/generators.py @@ -1,3 +1,4 @@ +import copy import logging import re from collections import OrderedDict, defaultdict @@ -388,7 +389,7 @@ class OpenAPISchemaGenerator(object): if method in overrides: overrides = overrides[method] - return overrides + return copy.deepcopy(overrides) def get_path_parameters(self, path, view_cls): """Return a list of Parameter instances corresponding to any templated path variables. diff --git a/src/drf_yasg/utils.py b/src/drf_yasg/utils.py index 2ac44b8..3aa894b 100644 --- a/src/drf_yasg/utils.py +++ b/src/drf_yasg/utils.py @@ -11,10 +11,15 @@ from rest_framework.views import APIView logger = logging.getLogger(__name__) -#: used to forcibly remove the body of a request via :func:`.swagger_auto_schema` -no_body = object() -unset = object() +class no_body(object): + """Used as a sentinel value to forcibly remove the body of a request via :func:`.swagger_auto_schema`.""" + pass + + +class unset(object): + """Used as a sentinel value for function parameters not set by the caller where ``None`` would be a valid value.""" + pass def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_body=None, query_serializer=None, @@ -33,7 +38,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=unset, request_bo :param .inspectors.SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object; this overrides both the class-level ``swagger_schema`` attribute and the ``DEFAULT_AUTO_SCHEMA_CLASS`` setting, and can be set to ``None`` to prevent this operation from being generated - :param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :data:`.no_body`. The value given here + :param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :class:`.no_body`. The value given here will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``. A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters diff --git a/tests/test_schema_generator.py b/tests/test_schema_generator.py index 648b057..6a229f2 100644 --- a/tests/test_schema_generator.py +++ b/tests/test_schema_generator.py @@ -2,11 +2,14 @@ import json from collections import OrderedDict import pytest +from rest_framework import routers, serializers, viewsets +from rest_framework.response import Response from drf_yasg import codecs, openapi from drf_yasg.codecs import yaml_sane_load from drf_yasg.errors import SwaggerGenerationError from drf_yasg.generators import OpenAPISchemaGenerator +from drf_yasg.utils import swagger_auto_schema def test_schema_is_valid(swagger, codec_yaml): @@ -79,3 +82,34 @@ def test_securiy_requirements(swagger_settings, mock_schema_request): swagger = generator.get_schema(mock_schema_request, public=True) assert swagger['security'] == [] + + +def test_replaced_serializer(): + class DetailSerializer(serializers.Serializer): + detail = serializers.CharField() + + class DetailViewSet(viewsets.ViewSet): + serializer_class = DetailSerializer + + @swagger_auto_schema(responses={404: openapi.Response("Not found or Not accessible", DetailSerializer)}) + def retrieve(self, request, pk=None): + serializer = DetailSerializer({'detail': None}) + return Response(serializer.data) + + router = routers.DefaultRouter() + router.register(r'details', DetailViewSet, base_name='details') + + generator = OpenAPISchemaGenerator( + info=openapi.Info(title="Test generator", default_version="v1"), + version="v2", + url='', + patterns=router.urls + ) + + for _ in range(3): + swagger = generator.get_schema(None, True) + assert 'Detail' in swagger['definitions'] + assert 'detail' in swagger['definitions']['Detail']['properties'] + responses = swagger['paths']['/details/{id}/']['get']['responses'] + assert '404' in responses + assert responses['404']['schema']['$ref'] == "#/definitions/Detail"