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)
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
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
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
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': '' }