Exemple #1
0
    def _get_examples(self,
                      serializer,
                      direction,
                      media_type,
                      status_code=None):
        examples = self.get_examples()

        if not examples:
            if is_list_serializer(serializer):
                examples = get_override(serializer.child, 'examples', [])
            elif is_serializer(serializer):
                examples = get_override(serializer, 'examples', [])

        filtered_examples = []
        for example in examples:
            if direction == 'request' and example.response_only:
                continue
            if direction == 'response' and example.request_only:
                continue
            if media_type and media_type != example.media_type:
                continue
            if status_code and status_code not in example.status_codes:
                continue
            filtered_examples.append(example)

        return build_examples_list(filtered_examples)
Exemple #2
0
    def _is_list_view(self, serializer=None):
        """
        partially heuristic approach to determine if a view yields an object or a
        list of objects. used for operationId naming, array building and pagination.
        defaults to False if all introspection fail.
        """
        if serializer is None:
            serializer = self.get_response_serializers()

        if isinstance(serializer, dict) and serializer:
            # extract likely main serializer from @extend_schema override
            serializer = {str(code): s for code, s in serializer.items()}
            serializer = serializer[min(serializer)]

        if is_list_serializer(serializer):
            return True
        if is_basic_type(serializer):
            return False
        if hasattr(self.view, 'action'):
            return self.view.action == 'list'
        # list responses are "usually" only returned by GET
        if self.method.lower() != 'get':
            return False
        if isinstance(self.view, ListModelMixin):
            return True
        # primary key/lookup variable in path is a strong indicator for retrieve
        if isinstance(self.view, GenericAPIView):
            lookup_url_kwarg = self.view.lookup_url_kwarg or self.view.lookup_field
            if lookup_url_kwarg in uritemplate.variables(self.path):
                return False

        return False
Exemple #3
0
    def _get_serializer_name(self, serializer, direction):
        serializer_extension = OpenApiSerializerExtension.get_match(serializer)
        if serializer_extension and serializer_extension.get_name():
            # library override mechanisms
            name = serializer_extension.get_name()
        elif getattr(getattr(serializer, 'Meta', None), 'ref_name',
                     None) is not None:
            # local override mechanisms. for compatibility with drf-yasg we support meta ref_name,
            # though we do not support the serializer inlining feature.
            # https://drf-yasg.readthedocs.io/en/stable/custom_spec.html#serializer-meta-nested-class
            name = serializer.Meta.ref_name
        elif is_list_serializer(serializer):
            return self._get_serializer_name(serializer.child, direction)
        else:
            name = serializer.__class__.__name__

        if name.endswith('Serializer'):
            name = name[:-10]

        if self.method == 'PATCH' and spectacular_settings.COMPONENT_SPLIT_PATCH:
            if direction == 'request' and serializer.partial and not serializer.read_only:
                name = 'Patched' + name

        if direction == 'request' and spectacular_settings.COMPONENT_SPLIT_REQUEST:
            name = name + 'Request'

        return name
Exemple #4
0
    def _get_request_body(self):
        # only unsafe methods can have a body
        if self.method not in ('PUT', 'PATCH', 'POST'):
            return None

        serializer = force_instance(self.get_request_serializer())

        request_body_required = False
        if is_list_serializer(serializer):
            if is_serializer(serializer.child):
                component = self.resolve_serializer(serializer.child,
                                                    'request')
                schema = build_array_type(component.ref)
            else:
                schema = build_array_type(
                    self._map_serializer_field(serializer.child, 'request'))
            request_body_required = True
        elif is_serializer(serializer):
            if self.method == 'PATCH':
                serializer.partial = True
            component = self.resolve_serializer(serializer, 'request')
            if not component.schema:
                # serializer is empty so skip content enumeration
                return None
            schema = component.ref
            # request body is only required if any required property is not read-only
            readonly_props = [
                p for p, s in component.schema.get('properties', {}).items()
                if s.get('readOnly')
            ]
            required_props = component.schema.get('required', [])
            request_body_required = any(req not in readonly_props
                                        for req in required_props)
        elif is_basic_type(serializer):
            schema = build_basic_type(serializer)
            if not schema:
                return None
        else:
            warn(
                f'could not resolve request body for {self.method} {self.path}. defaulting to generic '
                'free-form object. (maybe annotate a Serializer class?)')
            schema = build_object_type(
                additionalProperties={},
                description='Unspecified request body',
            )

        request_body = {
            'content': {
                media_type: build_media_type_object(
                    schema,
                    self._get_examples(serializer, 'request', media_type))
                for media_type in self.map_parsers()
            }
        }

        if request_body_required:
            request_body['required'] = request_body_required

        return request_body
Exemple #5
0
    def _get_response_for_code(self,
                               serializer,
                               status_code,
                               media_types=None):
        serializer = force_instance(serializer)

        if not serializer:
            return {'description': _('No response body')}
        elif is_list_serializer(serializer):
            if is_serializer(serializer.child):
                schema = self.resolve_serializer(serializer.child,
                                                 'response').ref
            else:
                schema = self._map_serializer_field(serializer.child,
                                                    'response')
        elif is_serializer(serializer):
            component = self.resolve_serializer(serializer, 'response')
            if not component.schema:
                return {'description': _('No response body')}
            schema = component.ref
        elif is_basic_type(serializer):
            schema = build_basic_type(serializer)
        elif isinstance(serializer, dict):
            # bypass processing and use given schema directly
            schema = serializer
        else:
            warn(
                f'could not resolve "{serializer}" for {self.method} {self.path}. Expected either '
                f'a serializer or some supported override mechanism. defaulting to '
                f'generic free-form object.')
            schema = build_basic_type(OpenApiTypes.OBJECT)
            schema['description'] = _('Unspecified response body')

        if self._is_list_view(serializer) and not get_override(
                serializer, 'many') is False:
            schema = build_array_type(schema)
            paginator = self._get_paginator()

            if paginator and is_serializer(serializer):
                paginated_name = f'Paginated{self._get_serializer_name(serializer, "response")}List'
                component = ResolvedComponent(
                    name=paginated_name,
                    type=ResolvedComponent.SCHEMA,
                    schema=paginator.get_paginated_response_schema(schema),
                    object=serializer.child
                    if is_list_serializer(serializer) else serializer,
                )
                self.registry.register_on_missing(component)
                schema = component.ref
            elif paginator:
                schema = paginator.get_paginated_response_schema(schema)

        if not media_types:
            media_types = self.map_renderers('media_type')

        return {
            'content': {
                media_type: build_media_type_object(
                    schema,
                    self._get_examples(serializer, 'response', media_type,
                                       status_code))
                for media_type in media_types
            },
            # Description is required by spec, but descriptions for each response code don't really
            # fit into our model. Description is therefore put into the higher level slots.
            # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#responseObject
            'description': ''
        }