def test_customized_list_serializer(): class X(models.Model): position = models.IntegerField() class XSerializer(serializers.ModelSerializer): class Meta: model = X fields = ("id", "position") class XListUpdateSerializer(serializers.ListSerializer): child = XSerializer() class XAPIView(generics.GenericAPIView): model = X serializer_class = XListUpdateSerializer def put(self, request, *args, **kwargs): pass # pragma: no cover schema = generate_schema('x', view=XAPIView) operation = schema['paths']['/x']['put'] comp = '#/components/schemas/X' assert get_request_schema(operation)['type'] == 'array' assert get_request_schema(operation)['items']['$ref'] == comp assert get_response_schema(operation)['type'] == 'array' assert get_response_schema(operation)['items']['$ref'] == comp assert operation['operationId'] == 'x_update' assert len(schema['components'] ['schemas']) == 1 and 'X' in schema['components']['schemas']
def test_api_view_decorator_multi(no_warnings): @extend_schema(request=OpenApiTypes.FLOAT, responses=OpenApiTypes.INT, methods=['POST']) @extend_schema(responses=OpenApiTypes.FLOAT, methods=['GET']) @api_view(['GET', 'POST']) def pi(request): pass # pragma: no cover schema = generate_schema('x', view_function=pi) operation = schema['paths']['/x']['get'] assert get_response_schema(operation)['type'] == 'number' operation = schema['paths']['/x']['post'] assert get_request_schema(operation)['type'] == 'number' assert get_response_schema(operation)['type'] == 'integer'
def test_inline_serializer(no_warnings): @extend_schema(responses=inline_serializer( name='InlineOneOffSerializer', fields={ 'char': serializers.CharField(), 'choice': serializers.ChoiceField(choices=(('A', 'A'), ('B', 'B'))), 'nested_inline': inline_serializer( name='NestedInlineOneOffSerializer', fields={ 'char': serializers.CharField(), 'int': serializers.IntegerField(), }, allow_null=True, ) })) @api_view(['GET']) def one_off(request, foo): pass # pragma: no cover schema = generate_schema('x', view_function=one_off) assert get_response_schema(schema['paths']['/x']['get'])['$ref'] == ( '#/components/schemas/InlineOneOff') assert len(schema['components']['schemas']) == 3 one_off = schema['components']['schemas']['InlineOneOff'] one_off_nested = schema['components']['schemas']['NestedInlineOneOff'] assert len(one_off['properties']) == 3 assert one_off['properties']['nested_inline']['nullable'] is True assert one_off['properties']['nested_inline']['allOf'][0]['$ref'] == ( '#/components/schemas/NestedInlineOneOff') assert len(one_off_nested['properties']) == 2
def test_many_polymorphic_serializer_extend_schema(no_warnings, explicit): if explicit: proxy_serializer = serializers.ListSerializer( child=PolymorphicProxySerializer(**PROXY_SERIALIZER_PARAMS)) else: proxy_serializer = PolymorphicProxySerializer(** PROXY_SERIALIZER_PARAMS, many=True) @extend_schema(request=proxy_serializer, responses=proxy_serializer) @api_view(['POST']) def view_func(request, format=None): pass # pragma: no cover schema = generate_schema('/x/', view_function=view_func) assert 'MetaPerson' in schema['components']['schemas'] op = schema['paths']['/x/']['post'] assert get_response_schema(op) == { 'type': 'array', 'items': { '$ref': '#/components/schemas/MetaPerson' } } assert get_request_schema(op) == { 'type': 'array', 'items': { '$ref': '#/components/schemas/MetaPerson' } }
def test_lib_serializer_naming_collision_resolution(no_warnings): """ parity test in tests.test_warnings.test_serializer_name_reuse """ def x_lib1(): class XSerializer(serializers.Serializer): x = serializers.UUIDField() return XSerializer def x_lib2(): class XSerializer(serializers.Serializer): x = serializers.IntegerField() return XSerializer x_lib1, x_lib2 = x_lib1(), x_lib2() class XAPIView(APIView): @extend_schema(request=x_lib1, responses=x_lib2) def post(self, request): pass # pragma: no cover class Lib2XSerializerRename(OpenApiSerializerExtension): target_class = x_lib2 # also accepts import strings def get_name(self): return 'RenamedLib2X' schema = generate_schema('x', view=XAPIView) operation = schema['paths']['/x']['post'] assert get_request_schema(operation)['$ref'] == '#/components/schemas/X' assert get_response_schema( operation)['$ref'] == '#/components/schemas/RenamedLib2X'
def test_owned_serializer_naming_override_with_ref_name(no_warnings): def x_owned1(): class XSerializer(serializers.Serializer): x = serializers.UUIDField() return XSerializer def x_owned2(): class XSerializer(serializers.Serializer): x = serializers.IntegerField() class Meta: ref_name = 'Y' return XSerializer x_owned1, x_owned2 = x_owned1(), x_owned2() class XAPIView(APIView): @extend_schema(request=x_owned1, responses=x_owned2) 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 get_response_schema(operation)['$ref'] == '#/components/schemas/Y'
def test_schema_contains_only_urlpatterns_first_match(no_warnings): class XSerializer(serializers.Serializer): integer = serializers.IntegerField() class XAPIView(APIView): @extend_schema(responses=XSerializer) def get(self, request): pass # pragma: no cover class YSerializer(serializers.Serializer): integer = serializers.DateTimeField() class YAPIView(APIView): @extend_schema(responses=YSerializer) def get(self, request): pass # pragma: no cover urlpatterns = [ path('api/x/', XAPIView.as_view()), # only first occurrence is used path('api/x/', YAPIView.as_view()), ] generator = SchemaGenerator(patterns=urlpatterns) schema = generator.get_schema(request=None, public=True) validate_schema(schema) assert len(schema['components']['schemas']) == 1 assert 'X' in schema['components']['schemas'] operation = schema['paths']['/api/x/']['get'] assert '#/components/schemas/X' in get_response_schema(operation)['$ref']
def test_string_response_variations(no_warnings, responses): @extend_schema(responses=responses) @api_view(['GET']) def view_func(request, format=None): pass # pragma: no cover schema = generate_schema('x', view_function=view_func) assert get_response_schema(schema['paths']['/x']['get'])['type'] == 'string'
def test_serializer_class_on_apiview(no_warnings): class XSerializer(serializers.Serializer): field = serializers.UUIDField() class XView(views.APIView): serializer_class = XSerializer # not supported by DRF but pick it up anyway def get(self, request): pass # pragma: no cover def post(self, request): pass # pragma: no cover schema = generate_schema('x', view=XView) comp = '#/components/schemas/X' assert get_response_schema(schema['paths']['/x']['get'])['$ref'] == comp assert get_response_schema(schema['paths']['/x']['post'])['$ref'] == comp assert schema['paths']['/x']['post']['requestBody']['content']['application/json']['schema']['$ref'] == comp
def test_api_view_decorator(no_warnings): @extend_schema(responses=OpenApiTypes.FLOAT) @api_view(['GET']) def pi(request): pass # pragma: no cover schema = generate_schema('x', view_function=pi) operation = schema['paths']['/x']['get'] assert get_response_schema(operation)['type'] == 'number'
def test_viewset_list_with_envelope(no_warnings): class XSerializer(serializers.Serializer): x = serializers.IntegerField() def enveloper(serializer_class, list): @extend_schema_serializer(many=False) class EnvelopeSerializer(serializers.Serializer): status = serializers.BooleanField() data = XSerializer(many=list) class Meta: ref_name = 'Enveloped{}{}'.format( serializer_class.__name__.replace("Serializer", ""), "List" if list else "", ) return EnvelopeSerializer class XViewset(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): @extend_schema(responses=enveloper(XSerializer, True)) def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) # pragma: no cover @extend_schema( responses=enveloper(XSerializer, False), parameters=[OpenApiParameter('id', int, OpenApiParameter.PATH)], ) def retrieve(self, request, *args, **kwargs): return super().retrieve(request, *args, **kwargs) # pragma: no cover schema = generate_schema('x', viewset=XViewset) operation_list = schema['paths']['/x/']['get'] assert operation_list['operationId'] == 'x_list' assert get_response_schema( operation_list)['$ref'] == '#/components/schemas/EnvelopedXList' operation_retrieve = schema['paths']['/x/{id}/']['get'] assert operation_retrieve['operationId'] == 'x_retrieve' assert get_response_schema( operation_retrieve)['$ref'] == '#/components/schemas/EnvelopedX'
def test_list_api_view(no_warnings): class XSerializer(serializers.Serializer): id = serializers.IntegerField() class XView(generics.ListAPIView): serializer_class = XSerializer schema = generate_schema('/x', view=XView) operation = schema['paths']['/x']['get'] assert operation['operationId'] == 'x_list' assert get_response_schema(operation)['type'] == 'array'
def test_view_function_extension(no_warnings): class FixXFunctionView(OpenApiViewExtension): target_class = 'tests.test_extensions.x_view_function' def view_replacement(self): fixed = extend_schema(responses=OpenApiTypes.FLOAT)(self.target_class) return fixed schema = generate_schema('x', view_function=x_view_function) operation = schema['paths']['/x']['get'] assert get_response_schema(operation)['type'] == 'number'
def test_layered_extend_schema_on_view_and_method_with_serializer(no_warnings): class ASerializer(serializers.Serializer): field = serializers.IntegerField() class BSerializer(serializers.Serializer): field = serializers.IntegerField() class CSerializer(serializers.Serializer): field = serializers.IntegerField() @extend_schema(responses=BSerializer) class XViewset(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): serializer_class = ASerializer @extend_schema(responses=CSerializer) def create(self, request, *args, **kwargs): super().create(request, *args, **kwargs) # pragma: no cover @extend_schema(responses=CSerializer) @action(detail=False, methods=['GET']) def extended_action(self, request): return Response() # pragma: no cover @action(detail=False, methods=['GET']) def raw_action(self, request): return Response() # pragma: no cover schema = generate_schema('x', XViewset) create_op = get_response_schema(schema['paths']['/x/']['post']) list_op = get_response_schema(schema['paths']['/x/']['get']) raw_action_op = get_response_schema( schema['paths']['/x/raw_action/']['get']) extended_action_op = get_response_schema( schema['paths']['/x/extended_action/']['get']) assert create_op['$ref'].endswith('C') assert extended_action_op['$ref'].endswith('C') assert list_op['items']['$ref'].endswith('B') assert raw_action_op['$ref'].endswith('B')
def test_view_extension(no_warnings): class FixXView(OpenApiViewExtension): target_class = 'tests.test_extensions.XView' def view_replacement(self): class Fixed(self.target_class): @extend_schema(responses=OpenApiTypes.FLOAT) def get(self, request): pass # pragma: no cover return Fixed schema = generate_schema('x', view=XView) operation = schema['paths']['/x']['get'] assert get_response_schema(operation)['type'] == 'number'
def test_list_serializer_with_field_child_on_extend_schema(): class XAPIView(APIView): @extend_schema( request=serializers.ListSerializer(child=serializers.IntegerField()), responses=serializers.ListSerializer(child=serializers.IntegerField()), ) def post(self, request): pass # pragma: no cover schema = generate_schema('x', view=XAPIView) req_schema = get_request_schema(schema['paths']['/x']['post']) res_schema = get_response_schema(schema['paths']['/x']['post']) for s in [req_schema, res_schema]: assert s['type'] == 'array' assert s['items']['type'] == 'integer'
def test_list_serializer_with_pagination(no_warnings): class GenreSerializer(serializers.Serializer): genre = serializers.CharField() class XViewSet(viewsets.GenericViewSet): pagination_class = pagination.LimitOffsetPagination @extend_schema(responses=GenreSerializer(many=True)) @action(methods=["GET"], detail=False) def genre(self, request, *args, **kwargs): pass # pragma: no cover schema = generate_schema('/x', XViewSet) response = get_response_schema(schema['paths']['/x/genre/']['get']) assert response['$ref'] == '#/components/schemas/PaginatedGenreList' assert 'PaginatedGenreList' in schema['components']['schemas'] assert 'Genre' in schema['components']['schemas']
def test_file_field_duality_on_split_request(no_warnings): class XSerializer(serializers.Serializer): file = serializers.FileField() class XView(generics.ListCreateAPIView): serializer_class = XSerializer parser_classes = [parsers.MultiPartParser] schema = generate_schema('/x', view=XView) assert get_response_schema( schema['paths']['/x']['get'] )['items']['$ref'] == '#/components/schemas/X' assert get_request_schema( schema['paths']['/x']['post'], content_type='multipart/form-data' )['$ref'] == '#/components/schemas/XRequest' assert schema['components']['schemas']['X']['properties']['file']['format'] == 'uri' assert schema['components']['schemas']['XRequest']['properties']['file']['format'] == 'binary'
def test_list_on_apiview_get(no_warnings): class XSerializer(serializers.Serializer): id = serializers.UUIDField() class XApiView(APIView): authentication_classes = [] @extend_schema( parameters=[OpenApiParameter('id', OpenApiTypes.INT, OpenApiParameter.PATH)], responses={200: XSerializer(many=True)}, ) def get(self, request): pass # pragma: no cover schema = generate_schema('x', view=XApiView) operation = schema['paths']['/x']['get'] assert operation['operationId'] == 'x_list' operation_schema = get_response_schema(operation) assert operation_schema['type'] == 'array'
def test_list_serializer_with_field_child(): class XSerializer(serializers.Serializer): field = serializers.ListSerializer(child=serializers.IntegerField()) class XAPIView(views.APIView): serializer_class = XSerializer def post(self, request, *args, **kwargs): pass # pragma: no cover # assumption on Serializer functionality assert XSerializer({'field': [1, 2, 3]}).data['field'] == [1, 2, 3] schema = generate_schema('x', view=XAPIView) assert get_request_schema(schema['paths']['/x']['post'])['$ref'] == '#/components/schemas/X' assert get_response_schema(schema['paths']['/x']['post'])['$ref'] == '#/components/schemas/X' properties = schema['components']['schemas']['X']['properties'] assert properties['field']['type'] == 'array' assert properties['field']['items']['type'] == 'integer'
def test_component_split_request(): class XSerializer(serializers.Serializer): ro = serializers.IntegerField(read_only=True) rw = serializers.IntegerField() wo = serializers.IntegerField(write_only=True) @extend_schema(request=XSerializer, responses=XSerializer) @api_view(['POST']) def pi(request, format=None): pass # pragma: no cover schema = generate_schema('/x', view_function=pi) operation = schema['paths']['/x']['post'] assert get_response_schema(operation)['$ref'] == '#/components/schemas/X' assert get_request_schema(operation)['$ref'] == '#/components/schemas/XRequest' assert len(schema['components']['schemas']['X']['properties']) == 2 assert 'wo' not in schema['components']['schemas']['X']['properties'] assert len(schema['components']['schemas']['XRequest']['properties']) == 2 assert 'ro' not in schema['components']['schemas']['XRequest']['properties']
def test_many_polymorphic_proxy_serializer_extend_schema_field( no_warnings, explicit): if explicit: proxy_serializer = serializers.ListField( child=PolymorphicProxySerializer(**PROXY_SERIALIZER_PARAMS)) else: proxy_serializer = PolymorphicProxySerializer(** PROXY_SERIALIZER_PARAMS, many=True) @extend_schema_field(proxy_serializer) class XField(serializers.DictField): pass # pragma: no cover class XSerializer(serializers.Serializer): field = XField() @extend_schema(request=XSerializer, responses=XSerializer) @api_view(['POST']) def view_func(request, format=None): pass # pragma: no cover schema = generate_schema('/x/', view_function=view_func) assert 'MetaPerson' in schema['components']['schemas'] assert schema['components']['schemas']['X'] == { 'type': 'object', 'properties': { 'field': { 'type': 'array', 'items': { '$ref': '#/components/schemas/MetaPerson' } } }, 'required': ['field'] } op = schema['paths']['/x/']['post'] assert get_request_schema(op) == {'$ref': '#/components/schemas/X'} assert get_response_schema(op) == {'$ref': '#/components/schemas/X'}