def get_paginated_response(self, paginator, response_schema): assert 'data' in response_schema[ 'properties'], "expected data field in response" assert response_schema['properties'][ 'data'].type == openapi.TYPE_ARRAY, "array expected for paged response" if not isinstance(paginator, (pagination.JsonApiPageNumberPagination, pagination.JsonApiLimitOffsetPagination)): return inspectors.NotHandled has_page = isinstance(paginator, pagination.JsonApiPageNumberPagination) meta_schema = openapi.Schema( type=openapi.TYPE_OBJECT, properties=OrderedDict(pagination=openapi.Schema( type=openapi.TYPE_OBJECT, properties=OrderedDict( filter_none(( ('count', openapi.Schema(type=openapi.TYPE_INTEGER)), ('page', openapi.Schema( type=openapi.TYPE_INTEGER) if has_page else None), ('pages', openapi.Schema( type=openapi.TYPE_INTEGER) if has_page else None), ('limit', openapi.Schema(type=openapi.TYPE_INTEGER ) if not has_page else None), ('offset', openapi.Schema(type=openapi.TYPE_INTEGER ) if not has_page else None), ))), ))) links_schema = openapi.Schema( type=openapi.TYPE_OBJECT, properties=OrderedDict(( ('first', openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI)), ('next', openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI)), ('last', openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI)), ('prev', openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI)), )), ) return openapi.Schema( type=openapi.TYPE_OBJECT, properties=filter_none( OrderedDict( links=links_schema, meta=meta_schema, data=response_schema.properties['data'], included=response_schema.properties.get('included'))))
def build_serializer_schema(self, serializer, resource_name, SwaggerType, ChildSwaggerType, use_references, is_request=None): fields = json_api_utils.get_serializer_fields(serializer) is_post = self.method.lower() == 'post' id_field = self.extract_id_field(fields, serializer) if id_field is None and not (is_request and is_post): logging.warning( '{view}.{serializer} does not contain id field as every resource should' .format(view=self.view.__class__.__name__, serializer=serializer.__class__.__name__)) attributes, req_attrs = self.extract_attributes( id_field, fields, ChildSwaggerType, use_references, is_request) relationships, req_rels = self.extract_relationships( fields, ChildSwaggerType, use_references, is_request) links = self.extract_links(fields, ChildSwaggerType, use_references) if not is_request else None schema_fields = filter_none( OrderedDict( type=self.build_type_schema(resource_name), id=self.probe_field_inspectors(id_field, ChildSwaggerType, use_references) if id_field and not (self.strip_read_fields_from_request and is_request and is_post) else None, attributes=openapi.Schema(type=openapi.TYPE_OBJECT, properties=attributes, required=req_attrs) if attributes else None, relationships=openapi.Schema(type=openapi.TYPE_OBJECT, properties=relationships, required=req_rels) if relationships else None, links=openapi.Schema(type=openapi.TYPE_OBJECT, properties=links) if links else None)) required_properties = None if self.is_request_or_unknown(is_request): required_properties = filter_none([ 'type', 'id' if 'id' in schema_fields else None, 'attributes' if req_attrs else None, 'relationships' if req_rels else None, ]) return SwaggerType(type=openapi.TYPE_OBJECT, properties=schema_fields, required=required_properties)
def make_schema_definition(): properties = OrderedDict() required = [] for property_name, child in serializer.fields.items(): prop_kwargs = {"read_only": bool(child.read_only) or None} prop_kwargs = filter_none(prop_kwargs) child_schema = self.probe_field_inspectors( child, ChildSwaggerType, use_references, **prop_kwargs) properties[property_name] = child_schema if child.required and not getattr(child_schema, "read_only", False): required.append(property_name) result = SwaggerType( type=openapi.TYPE_OBJECT, properties=properties, required=required or None, ) if not ref_name and "title" in result: # on an inline model, the title is derived from the field name # but is visually displayed like the model name, which is confusing # it is better to just remove title from inline models del result.title # Provide an option to add manual paremeters to a schema # for example, to add examples # self.add_manual_fields(serializer, result) return self.process_result(result, None, None)
def get_operation(self, operation_keys=None): operation_keys = operation_keys or self.operation_keys consumes = self.get_consumes() produces = self.get_produces() body = self.get_request_body_parameters(consumes) query = self.get_query_parameters() parameters = body + query parameters = filter_none(parameters) parameters = self.add_manual_parameters(parameters) operation_id = self.get_operation_id(operation_keys) summary, description = self.get_summary_and_description() security = self.get_security() assert security is None or isinstance(security, list), \ "security must be a list of security requirement objects" deprecated = self.is_deprecated() tags = self.get_tags(operation_keys) responses = self.get_responses() return openapi.Operation( operation_id=operation_id, description=force_real_str(description), summary=force_real_str(summary), responses=responses, parameters=parameters, consumes=consumes, produces=produces, tags=tags, security=security, deprecated=deprecated, **{'x-code-samples': self.overrides.get('code_examples')})
def extract_links(self, fields, ChildSwaggerType, use_references): self_field_name = api_settings.URL_FIELD_NAME return filter_none( OrderedDict(self=openapi.Schema( type=openapi.TYPE_STRING, format=openapi.FORMAT_URI ) if self_field_name in fields and isinstance( fields[self_field_name], serializers.RelatedField) else None))
def build_json_resource_schema(self, serializer, resource_name, SwaggerType, ChildSwaggerType, use_references, is_request=None): fields = json_api_utils.get_serializer_fields(serializer) id_ = fields.get('id') if id_ is None and self.method.lower() == 'get': logging.warning( '{view}.{serializer} does not contain id field as every resource should' .format(view=self.view.__class__.__name__, serializer=serializer.__class__.__name__)) if id_ is not None and id_.read_only and is_request: id_ = None attributes, req_attributes = self.extract_attributes( fields, ChildSwaggerType, use_references, is_request) relationships, req_relationships = self.extract_relationships( fields, ChildSwaggerType, use_references, is_request) links = self.extract_links(fields, ChildSwaggerType, use_references) if not is_request else None schema_fields = filter_none( OrderedDict( type=SwaggerType(type=openapi.TYPE_STRING, pattern=resource_name), id=self.probe_field_inspectors( id_, ChildSwaggerType, use_references) if id_ else None, attributes=SwaggerType(type=openapi.TYPE_OBJECT, properties=attributes, required=req_attributes) if attributes else None, relationships=SwaggerType(type=openapi.TYPE_OBJECT, properties=relationships, required=req_relationships) if relationships else None, links=SwaggerType(type=openapi.TYPE_OBJECT, properties=links) if links else None)) if self.is_request_or_unknown(is_request): required_properties = ['id', 'type' ] if 'id' in schema_fields else ['type'] else: required_properties = None return SwaggerType(type=openapi.TYPE_OBJECT, properties=schema_fields, required=required_properties)
def get_field_properties(serializer_class): """ serializer_class의 field들의 schema들을 dictionary 타입으로 만들어 반환 """ properties = OrderedDict() for property_name, child in serializer_class().fields.items(): prop_kwargs = {'read_only': bool(child.read_only) or None} prop_kwargs = filter_none(prop_kwargs) child_schema = SchemaGenerator._probe_field_inspectors( child, **prop_kwargs) properties[property_name] = child_schema return properties
def get_required_fields(serializer_class): """ serializer_class의 required field들의 list 반환 """ required = [] for property_name, child in serializer_class().fields.items(): prop_kwargs = {'read_only': bool(child.read_only) or None} prop_kwargs = filter_none(prop_kwargs) child_schema = SchemaGenerator._probe_field_inspectors( child, **prop_kwargs) if child.required and not getattr(child_schema, 'read_only', False): required.append(property_name) return required
def get_operation(self, operation_keys): consumes = self.get_consumes() produces = self.get_produces() multipart = ['multipart/form-data', 'application/x-www-form-urlencoded'] if 'file' in [param['type'] for param in self.get_request_body_parameters(multipart)]: # automatically set the media type to form data if there's a file # needed due to https://github.com/axnsan12/drf-yasg/issues/386 consumes = multipart body = self.get_request_body_parameters(consumes) query = self.get_query_parameters() parameters = body + query parameters = filter_none(parameters) parameters = self.add_manual_parameters(parameters) if 'bindings' in self.request.query_params: operation_id = self.overrides.get('operation_id', '') if not operation_id: operation_id = operation_keys[-1] else: operation_id = self.get_operation_id(operation_keys) summary, description = self.get_summary_and_description() security = self.get_security() assert security is None or isinstance(security, list), "security must be a list of " \ "security requirement objects" deprecated = self.is_deprecated() tags = self.get_tags(operation_keys) responses = self.get_responses() if 'operation_summary' not in self.overrides: summary = self.get_summary(operation_keys) return openapi.Operation( operation_id=operation_id, description=force_real_str(description), summary=force_real_str(summary), responses=responses, parameters=parameters, consumes=consumes, produces=produces, tags=tags, security=security, deprecated=deprecated )
def get_default_responses(self): if not is_json_api_response(self.get_renderer_classes()): return super().get_default_responses() method = self.method.lower() default_status = guess_response_status(method) default_schema = '' if method in ('get', 'post', 'put', 'patch'): default_serializer = self.get_default_response_serializer() default_schema = openapi.Schema( type=openapi.TYPE_OBJECT, properties=OrderedDict({ 'data': self.get_default_response_data(default_serializer), 'included': self.get_default_response_included(default_serializer) }) ) return filter_none(OrderedDict({str(default_status): default_schema}))
def get_operation(self, operation_keys): consumes = self.get_consumes() produces = self.get_produces() multipart = [ 'multipart/form-data', 'application/x-www-form-urlencoded' ] if self.method != 'GET': request_params = self.get_request_body_parameters(multipart) type_list = [param['type'] for param in request_params if param] if 'file' in type_list: # automatically set the media type to form data if there's a file # needed due to https://github.com/axnsan12/drf-yasg/issues/386 consumes = multipart body = self.get_request_body_parameters(consumes) query = self.get_query_parameters() if self.method == 'GET': fields_paramenter = Parameter( name="fields", in_="query", description="A list of fields to include in the response.", required=False, type="string", ) query.append(fields_paramenter) not_fields_paramenter = Parameter( name="exclude_fields", in_="query", description="A list of fields to exclude from the response.", required=False, type="string", ) query.append(not_fields_paramenter) parameters = body + query parameters = filter_none(parameters) parameters = self.add_manual_parameters(parameters) if 'bindings' in self.request.query_params: operation_id = self.overrides.get('operation_id', '') if not operation_id: operation_id = operation_keys[-1] else: operation_id = self.get_operation_id(operation_keys) summary, description = self.get_summary_and_description() if "include_html" not in self.request.query_params: description = strip_tags(description) security = self.get_security() assert security is None or isinstance(security, list), "security must be a list of " \ "security requirement objects" deprecated = self.is_deprecated() tags = self.get_tags(operation_keys) responses = self.get_responses() if 'operation_summary' not in self.overrides: summary = self.get_summary(operation_keys) return openapi.Operation(operation_id=operation_id, description=force_real_str(description), summary=force_real_str(summary), responses=responses, parameters=parameters, consumes=consumes, produces=produces, tags=tags, security=security, deprecated=deprecated)
def get_operation(self, operation_keys): consumes = self.get_consumes() produces = self.get_produces() multipart = [ "multipart/form-data", "application/x-www-form-urlencoded" ] if self.method != "GET": contains_file_field = False serializer = self.get_request_serializer() for field_name, field in getattr(serializer, "fields", {}).items(): if isinstance(field, serializers.FileField): contains_file_field = True break if contains_file_field: # automatically set the media type to form data if there's a file # needed due to https://github.com/axnsan12/drf-yasg/issues/386 consumes = multipart body = self.get_request_body_parameters(consumes) query = self.get_query_parameters() if self.method == "GET": fields_paramenter = Parameter( name="fields", in_="query", description="A list of fields to include in the response.", required=False, type="string", ) query.append(fields_paramenter) not_fields_paramenter = Parameter( name="exclude_fields", in_="query", description="A list of fields to exclude from the response.", required=False, type="string", ) query.append(not_fields_paramenter) parameters = body + query parameters = filter_none(parameters) parameters = self.add_manual_parameters(parameters) if "bindings" in self.request.query_params: operation_id = self.overrides.get("operation_id", "") if not operation_id: operation_id = operation_keys[-1] else: operation_id = self.get_operation_id(operation_keys) summary, description = self.get_summary_and_description() if "include_html" not in self.request.query_params: description = strip_tags(description) security = self.get_security() assert security is None or isinstance( security, list), ("security must be a list of " "security requirement objects") deprecated = self.is_deprecated() tags = self.get_tags(operation_keys) responses = self.get_responses() if "operation_summary" not in self.overrides: summary = self.get_summary(operation_keys) return openapi.Operation( operation_id=operation_id, description=force_real_str(description), summary=force_real_str(summary), responses=responses, parameters=parameters, consumes=consumes, produces=produces, tags=tags, security=security, deprecated=deprecated, )
def extract_relationships(self, fields, ChildSwaggerType, use_references, is_request=None): relationships = OrderedDict() required_relationships = [] for field_name, field in fields.items(): self.maybe_fix_broken_parent_relation(field) if self.should_strip_from_schema(field, is_request): continue # Self url field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations if not isinstance( field, (relations.RelatedField, relations.ManyRelatedField, serializers.Serializer)): continue # Produce swagger output relation_data_schema = openapi.Schema(**filter_none( OrderedDict( type=openapi.TYPE_OBJECT, properties=OrderedDict( id=self.probe_field_inspectors(field, ChildSwaggerType, use_references), type=self.build_type_schema( self.get_resource_name_from_related_id_field( field_name, field), read_only=field.read_only), ), required=['id', 'type'] if (self.is_request_or_unknown( is_request)) and not field.read_only else None, ))) if is_many_related_field(field): relation_data_schema = openapi.Schema( type=openapi.TYPE_ARRAY, items=relation_data_schema) relation_links_schema = self.get_links_from_id_field( field_name, field) if relation_links_schema: relation_links_schema = openapi.Schema( type=openapi.TYPE_OBJECT, properties=relation_links_schema) is_relation_required = self.is_request_or_unknown( is_request) and field.required and not field.read_only relationships[field_name] = openapi.Schema(**filter_none( OrderedDict( type=openapi.TYPE_OBJECT, properties=filter_none({ 'data': relation_data_schema, 'links': relation_links_schema if self. not_request_or_unknown(is_request) else None }), required=['data'] if is_relation_required else None, read_only=field.read_only or None, x_read_only=field.read_only or None, ))) if is_relation_required: required_relationships.append(field_name) return relationships, (required_relationships or None)
def build_type_schema(self, resource_name, read_only=None): return openapi.Schema(**filter_none( OrderedDict(type=openapi.TYPE_STRING, pattern=resource_name, read_only=read_only or None)))
def extract_relationships(self, fields, ChildSwaggerType, use_references, is_request=None): relationships = OrderedDict() required_relationships = [] for field_name, field in fields.items(): many = False id_field = field if is_request and field.read_only: continue # Self url field if field_name == api_settings.URL_FIELD_NAME: continue # Skip fields without relations if not isinstance( field, (relations.RelatedField, relations.ManyRelatedField, serializers.Serializer)): continue # Unpack relation from many field if isinstance(id_field, serializers.ManyRelatedField): id_field = id_field.child_relation many = True resource_name = self.get_resource_name_from_id_field( field_name, id_field) # Pass swagger type evaluation to inspectors if getattr(id_field, 'pk_field', None): # a PrimaryKeyRelatedField can have a `pk_field` attribute which is a # serializer field that will convert the PK value swagger_id_field = self.probe_field_inspectors( id_field.pk_field, ChildSwaggerType, use_references) else: swagger_id_field = self.probe_field_inspectors( id_field, ChildSwaggerType, use_references) # Produce swagger output relation_data = openapi.Schema(**filter_none( OrderedDict( type=openapi.TYPE_OBJECT, properties={ 'type': openapi.Schema(**filter_none( OrderedDict(type=openapi.TYPE_STRING, pattern=resource_name, read_only=field.read_only or None))), 'id': swagger_id_field }, required=['id', 'type'] if (self.is_request_or_unknown( is_request)) and not field.read_only else None, ))) if many: relation_data = openapi.Schema(type=openapi.TYPE_ARRAY, items=relation_data) relation_links = self.get_links_from_id_field(field_name, field) if relation_links: relation_links = openapi.Schema(type=openapi.TYPE_OBJECT, properties=relation_links) relationships[field_name] = openapi.Schema(**filter_none( OrderedDict( type=openapi.TYPE_OBJECT, properties=filter_none({ 'data': relation_data, 'links': relation_links if self. not_request_or_unknown(is_request) else None }), read_only=field.read_only or None, x_read_only=field.read_only or None, ))) if self.is_request_or_unknown( is_request) and field.required and not field.read_only: required_relationships.append(field_name) return relationships, (required_relationships or None)