From 2227beb0e6cb21d419e1941c835e5320c1ff3ff4 Mon Sep 17 00:00:00 2001 From: "T. Franzel" Date: Thu, 20 May 2021 19:39:11 +0200 Subject: [PATCH] invert component exclusion logic (OpenApiSerializerExtension) #351 #391 --- drf_spectacular/openapi.py | 17 ++++++++--------- tests/test_regressions.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/drf_spectacular/openapi.py b/drf_spectacular/openapi.py index 3052c52a..9fc4b7f2 100644 --- a/drf_spectacular/openapi.py +++ b/drf_spectacular/openapi.py @@ -1179,16 +1179,15 @@ def resolve_serializer(self, serializer, direction) -> ResolvedComponent: self.registry.register(component) component.schema = self._map_serializer(serializer, direction) - # 4 cases: - # 1. polymorphic container component -> use - # 2. concrete component with properties -> use - # 3. concrete component without properties -> prob. transactional so discard - # 4. explicit list component -> demultiplexed at usage location so discard - keep_component = ( - any(nest_tag in component.schema for nest_tag in ['oneOf', 'allOf', 'anyOf']) - or component.schema.get('properties', {}) + + discard_component = ( + # components with empty schemas serve no purpose + not component.schema + # concrete component without properties are likely only transactional so discard + or (component.schema.get('type') == 'object' and not component.schema.get('properties')) ) - if not keep_component: + + if discard_component: del self.registry[component] return ResolvedComponent(None, None) # sentinel return component diff --git a/tests/test_regressions.py b/tests/test_regressions.py index b3540039..e6ce6d7c 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -864,7 +864,7 @@ class CustomAuth(TokenAuthentication): pass class XSerializer(serializers.Serializer): - field = serializers.IntegerField + field = serializers.IntegerField() class XAPIView(APIView): authentication_classes = [CustomAuth] @@ -1238,7 +1238,7 @@ def view_func(request, format=None): def test_basic_viewset_without_queryset_with_explicit_pk_typing(no_warnings): class XSerializer(serializers.Serializer): - field = fields.IntegerField() + field = serializers.IntegerField() class XViewset(viewsets.ViewSet): serializer_class = XSerializer @@ -1845,3 +1845,29 @@ def test_yaml_encoder_parity(no_warnings, value): # rest_framework.encoders.JSONEncoder assert OpenApiJsonRenderer().render(value) assert OpenApiYamlRenderer().render(value) + + +@pytest.mark.parametrize('comp_schema', [ + {'type': 'number'}, + {'type': 'array', 'items': {'type': 'number'}}, +]) +def test_serializer_extension_with_non_object_schema(no_warnings, comp_schema): + class XSerializer(serializers.Serializer): + field = serializers.CharField() + + class XExtension(OpenApiSerializerExtension): + target_class = XSerializer + + def map_serializer(self, auto_schema, direction): + return comp_schema + + class XAPIView(APIView): + @extend_schema(request=XSerializer, responses=XSerializer) + def post(self, request): + pass # pragma: no cover + + schema = generate_schema('x', view=XAPIView) + + operation = schema['paths']['/x']['post'] + assert get_request_schema(operation)['$ref'] == '#/components/schemas/X' + assert schema['components']['schemas']['X'] == comp_schema