def _process_override_parameters(self): result = [] for parameter in self.get_override_parameters(): if isinstance(parameter, OpenApiParameter): if is_basic_type(parameter.type): schema = build_basic_type(parameter.type) elif is_serializer(parameter.type): schema = self.resolve_serializer(parameter.type, direction=None).ref else: schema = parameter.type result.append(build_parameter_type( name=parameter.name, schema=schema, location=parameter.location, required=parameter.required, description=parameter.description, enum=parameter.enum, deprecated=parameter.deprecated, )) elif is_serializer(parameter): # explode serializer into separate parameters. defaults to QUERY location mapped = self._map_serializer(parameter, direction=None) for property_name, property_schema in mapped['properties'].items(): result.append(build_parameter_type( name=property_name, schema=property_schema, location=OpenApiParameter.QUERY, required=property_name in mapped.get('required', []) )) else: warn(f'could not resolve parameter annotation {parameter}. skipping.') return result
def resolve_filter_field(self, auto_schema, model, filterset_class, field_name, filter_field): from django_filters.rest_framework import filters if isinstance(filter_field, filters.OrderingFilter): # only here filter_field.field_name is not the model field name/path schema = build_basic_type(OpenApiTypes.STR) elif filter_field.method: if callable(filter_field.method): filter_method = filter_field.method else: filter_method = getattr(filterset_class, filter_field.method) try: filter_method_hints = typing.get_type_hints(filter_method) except: # noqa: E722 filter_method_hints = {} if 'value' in filter_method_hints and is_basic_type( filter_method_hints['value']): schema = build_basic_type(filter_method_hints['value']) else: schema = self.map_filter_field(filter_field) else: path = filter_field.field_name.split('__') model_field = follow_field_source(model, path) if isinstance(model_field, models.Field): schema = auto_schema._map_model_field(model_field, direction=None) else: schema = self.map_filter_field(filter_field) enum = schema.pop('enum', None) if 'choices' in filter_field.extra: enum = [c for c, _ in filter_field.extra['choices']] if enum: schema['enum'] = sorted(enum, key=str) description = schema.pop('description', None) if filter_field.extra.get('help_text', None): description = filter_field.extra['help_text'] elif filter_field.label is not None: description = filter_field.label if isinstance(filter_field, filters.BaseCSVFilter): schema = build_array_type(schema) explode = False style = 'form' else: explode = None style = None return build_parameter_type(name=field_name, required=filter_field.extra['required'], location=OpenApiParameter.QUERY, description=description, schema=schema, explode=explode, style=style)
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): if issubclass(self.target_class, SpectacularDjangoFilterBackendMixin): warn( 'DEPRECATED - Spectacular\'s DjangoFilterBackend is superseded by extension. you ' 'can simply restore this to the original class, extensions will take care of the ' 'rest.') model = get_view_model(auto_schema.view) if not model: return [] filterset_class = self.target.get_filterset_class( auto_schema.view, model.objects.none()) if not filterset_class: return [] parameters = [] for field_name, field in filterset_class.base_filters.items(): schema, description, enum = (self.resolve_filter_field( auto_schema, model, filterset_class, field)) parameters.append( build_parameter_type( name=field_name, required=field.extra['required'], location=OpenApiParameter.QUERY, description=description, schema=schema, enum=enum, )) return parameters
def resolve_filter_field(self, auto_schema, model, filterset_class, field_name, filter_field): """ Generate proper OAS for ObjectTypeFilter """ if isinstance(filter_field, ObjectTypeFilter): schema = build_basic_type(OpenApiTypes.URI) if "max_length" in filter_field.extra: schema["maxLength"] = filter_field.extra.get("max_length") if "min_length" in filter_field.extra: schema["minLength"] = filter_field.extra["min_length"] description = filter_field.extra["help_text"] return [ build_parameter_type( name=field_name, required=filter_field.extra["required"], location=OpenApiParameter.QUERY, description=description, schema=schema, ) ] return super().resolve_filter_field(auto_schema, model, filterset_class, field_name, filter_field)
def get_schema_operation_parameters(self, view): model = get_view_model(view) if not model: return [] filterset_class = self.get_filterset_class(view, model.objects.none()) if not filterset_class: return [] parameters = [] for field_name, field in filterset_class.base_filters.items(): path = field.field_name.split('__') model_field = follow_field_source(model, path) parameters.append( build_parameter_type( name=field_name, required=field.extra['required'], location=OpenApiParameter.QUERY, description=field.label if field.label is not None else field_name, schema=view.schema._map_model_field(model_field, direction=None), enum=[c for c, _ in field.extra.get('choices', [])], )) return parameters
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): if issubclass(self.target_class, SpectacularDjangoFilterBackendMixin): warn( 'DEPRECATED - Spectacular\'s DjangoFilterBackend is superseded by extension. you ' 'can simply restore this to the original class, extensions will take care of the ' 'rest.') model = get_view_model(auto_schema.view) if not model: return [] filterset_class = self.target.get_filterset_class( auto_schema.view, model.objects.none()) if not filterset_class: return [] parameters = [] for field_name, field in filterset_class.base_filters.items(): path = field.field_name.split('__') model_field = follow_field_source(model, path) parameters.append( build_parameter_type( name=field_name, required=field.extra['required'], location=OpenApiParameter.QUERY, description=field.label if field.label is not None else field_name, schema=auto_schema._map_model_field(model_field, direction=None), enum=[c for c, _ in field.extra.get('choices', [])], )) return parameters
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): """Describe query parameters""" return [ build_parameter_type( name=self.target.organization_slug, required=False, location=OpenApiParameter.QUERY, description=self.target.organization_slug_description, schema={'type': 'string'}, ), build_parameter_type( name=self.target.organization_id, required=False, location=OpenApiParameter.QUERY, description=self.target.organization_id_description, schema={'type': 'string'}, ) ]
def _get_format_parameters(self): parameters = [] formats = self.map_renderers('format') if api_settings.URL_FORMAT_OVERRIDE and len(formats) > 1: parameters.append( build_parameter_type(name=api_settings.URL_FORMAT_OVERRIDE, schema=build_basic_type(OpenApiTypes.STR), location=OpenApiParameter.QUERY, enum=formats)) return parameters
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): """Describe query parameters.""" return [ build_parameter_type( name=self.target.search_param, required=False, location="query", description=_("Search string"), schema={"type": "string"}, ) ]
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): """Describe query parameters.""" return [ build_parameter_type( name=self.target.ordering_param, required=False, location="query", schema={"type": "string"}, description=_("Field to use when ordering results."), ) ]
def _process_override_parameters(self): result = {} for parameter in self.get_override_parameters(): if isinstance(parameter, OpenApiParameter): if is_basic_type(parameter.type): schema = build_basic_type(parameter.type) elif is_serializer(parameter.type): schema = self.resolve_serializer(parameter.type, 'request').ref else: schema = parameter.type if parameter.exclude: result[parameter.name, parameter.location] = None else: result[parameter.name, parameter.location] = build_parameter_type( name=parameter.name, schema=schema, location=parameter.location, required=parameter.required, description=parameter.description, enum=parameter.enum, deprecated=parameter.deprecated, style=parameter.style, explode=parameter.explode, default=parameter.default, examples=build_examples_list(parameter.examples), ) elif is_serializer(parameter): # explode serializer into separate parameters. defaults to QUERY location mapped = self._map_serializer(parameter, 'request') for property_name, property_schema in mapped['properties'].items(): result[property_name, OpenApiParameter.QUERY] = build_parameter_type( name=property_name, schema=property_schema, description=property_schema.pop('description', None), location=OpenApiParameter.QUERY, required=property_name in mapped.get('required', []), ) else: warn(f'could not resolve parameter annotation {parameter}. skipping.') return result
def _resolve_path_parameters(self, variables): model = getattr(getattr(self.view, 'queryset', None), 'model', None) parameters = [] for variable in variables: schema = build_basic_type(OpenApiTypes.STR) description = '' resolved_parameter = resolve_regex_path_parameter( self.path_regex, variable, self.map_renderers('format'), ) if resolved_parameter: schema = resolved_parameter['schema'] elif not model: warn( f'could not derive type of path parameter "{variable}" because because it ' f'is untyped and {self.view.__class__} has no queryset. consider adding a ' f'type to the path (e.g. <int:{variable}>) or annotating the parameter ' f'type with @extend_schema. defaulting to "string".') else: try: model_field = model._meta.get_field(variable) schema = self._map_model_field(model_field, direction=None) # strip irrelevant meta data irrelevant_field_meta = [ 'readOnly', 'writeOnly', 'nullable', 'default' ] schema = { k: v for k, v in schema.items() if k not in irrelevant_field_meta } if 'description' not in schema and model_field.primary_key: description = get_pk_description(model, model_field) except django_exceptions.FieldDoesNotExist: warn( f'could not derive type of path parameter "{variable}" because ' f'model "{model}" did contain no such field. consider annotating ' f'parameter with @extend_schema. defaulting to "string".' ) parameters.append( build_parameter_type(name=variable, location=OpenApiParameter.PATH, description=description, schema=schema)) return parameters
def _resolve_path_parameters(self, variables): """ Resolve path parameters. Extended to omit undesired warns. """ model = getattr(getattr(self.view, "queryset", None), "model", None) parameters = [] for variable in variables: schema = build_basic_type(OpenApiTypes.STR) description = "" resolved_parameter = resolve_django_path_parameter( self.path_regex, variable, self.map_renderers("format"), ) if not resolved_parameter: resolved_parameter = resolve_regex_path_parameter( self.path_regex, variable) if resolved_parameter: schema = resolved_parameter["schema"] elif model: try: model_field = model._meta.get_field(variable) schema = self._map_model_field(model_field, direction=None) # strip irrelevant meta data irrelevant_field_meta = [ "readOnly", "writeOnly", "nullable", "default" ] schema = { k: v for k, v in schema.items() if k not in irrelevant_field_meta } if "description" not in schema and model_field.primary_key: description = get_pk_description(model, model_field) except FieldDoesNotExist: pass parameters.append( build_parameter_type( name=variable, location=OpenApiParameter.PATH, description=description, schema=schema, )) return parameters
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): """Describe query parameters.""" filter_fields = getattr(auto_schema.view, "filter_fields", None) if not filter_fields: return [] return [ build_parameter_type( name=field_name, required=False, location="query", schema={"type": "string"}, ) for field_name in filter_fields ]
def _resolve_path_parameters(self, variables): object_path = "uuid" if variables == [object_path] and self.view.basename == "object": model = get_view_model(self.view) object_field = model._meta.get_field("object") uuid_field = object_field.related_model._meta.get_field("uuid") schema = self._map_model_field(uuid_field, direction=None) return [ build_parameter_type(name=object_path, location=OpenApiParameter.PATH, schema=schema) ] return super()._resolve_path_parameters(variables)
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): """Describe query parameters.""" filter_fields = getattr(auto_schema.view, "filter_fields", None) if not filter_fields: return [] return [ build_parameter_type( name="{}__{}".format(field_name, lookup), required=False, location="query", schema={"type": "string"}, ) for field_name, config in filter_fields.items() for lookup in config.get("lookups", []) ]
def get_schema_operation_parameters(self, auto_schema, *args, **kwargs): """Describe query parameters.""" search_fields = getattr(auto_schema.view, "search_fields", None) if not search_fields: return [] return [ build_parameter_type( name=self.target.search_param, required=False, location="query", description=_("Search in %(fields)s") % { "fields": _(", ").join(search_fields), }, schema={"type": "string"}, ) ]
def _resolve_path_parameters(self, variables): parameters = [] for variable in variables: schema = build_basic_type(OpenApiTypes.STR) description = '' resolved_parameter = resolve_regex_path_parameter( self.path_regex, variable, self.map_renderers('format'), ) if resolved_parameter: schema = resolved_parameter['schema'] elif get_view_model(self.view) is None: warn( f'could not derive type of path parameter "{variable}" because because it ' f'is untyped and obtaining queryset from {self.view.__class__} failed. ' f'consider adding a type to the path (e.g. <int:{variable}>) or annotating ' f'the parameter type with @extend_schema. defaulting to "string".' ) else: try: model = get_view_model(self.view) model_field = model._meta.get_field(variable) schema = self._map_model_field(model_field, direction=None) if 'description' not in schema and model_field.primary_key: description = get_pk_description(model, model_field) except django_exceptions.FieldDoesNotExist: warn( f'could not derive type of path parameter "{variable}" because ' f'model "{model}" did contain no such field. consider annotating ' f'parameter with @extend_schema. defaulting to "string".' ) parameters.append( build_parameter_type(name=variable, location=OpenApiParameter.PATH, description=description, schema=schema)) return parameters
def resolve_filter_field(self, auto_schema, model, filterset_class, field_name, filter_field): from django_filters.rest_framework import filters unambiguous_mapping = { filters.CharFilter: OpenApiTypes.STR, filters.BooleanFilter: OpenApiTypes.BOOL, filters.DateFilter: OpenApiTypes.DATE, filters.DateTimeFilter: OpenApiTypes.DATETIME, filters.IsoDateTimeFilter: OpenApiTypes.DATETIME, filters.TimeFilter: OpenApiTypes.TIME, filters.UUIDFilter: OpenApiTypes.UUID, filters.DurationFilter: OpenApiTypes.DURATION, filters.OrderingFilter: OpenApiTypes.STR, filters.TimeRangeFilter: OpenApiTypes.TIME, filters.DateFromToRangeFilter: OpenApiTypes.DATE, filters.IsoDateTimeFromToRangeFilter: OpenApiTypes.DATETIME, filters.DateTimeFromToRangeFilter: OpenApiTypes.DATETIME, } if isinstance(filter_field, tuple(unambiguous_mapping) ) and filter_field.__class__ in unambiguous_mapping: schema = build_basic_type( unambiguous_mapping[filter_field.__class__]) elif isinstance(filter_field, (filters.NumberFilter, filters.NumericRangeFilter)): # NumberField is underspecified by itself. try to find the # type that makes the most sense or default to generic NUMBER if filter_field.method: schema = self._build_filter_method_type( filterset_class, filter_field) if schema['type'] not in ['integer', 'number']: schema = build_basic_type(OpenApiTypes.NUMBER) else: model_field = self._get_model_field(filter_field, model) if isinstance(model_field, (models.IntegerField, models.AutoField)): schema = build_basic_type(OpenApiTypes.INT) elif isinstance(model_field, models.FloatField): schema = build_basic_type(OpenApiTypes.FLOAT) elif isinstance(model_field, models.DecimalField): schema = build_basic_type( OpenApiTypes.NUMBER) # TODO may be improved else: schema = build_basic_type(OpenApiTypes.NUMBER) elif filter_field.method: # try to make best effort on the given method schema = self._build_filter_method_type(filterset_class, filter_field) else: # last resort is to lookup the type via the model field. model_field = self._get_model_field(filter_field, model) if isinstance(model_field, models.Field): schema = auto_schema._map_model_field(model_field, direction=None) else: # default to string if nothing else works schema = build_basic_type(OpenApiTypes.STR) # enrich schema with additional info from filter_field enum = schema.pop('enum', None) if 'choices' in filter_field.extra: enum = [c for c, _ in filter_field.extra['choices']] if enum: schema['enum'] = sorted(enum, key=str) description = schema.pop('description', None) if filter_field.extra.get('help_text', None): description = filter_field.extra['help_text'] elif filter_field.label is not None: description = filter_field.label # parameter style variations based on filter base class if isinstance(filter_field, filters.BaseCSVFilter): schema = build_array_type(schema) field_names = [field_name] explode = False style = 'form' elif isinstance(filter_field, filters.MultipleChoiceFilter): schema = build_array_type(schema) field_names = [field_name] explode = True style = 'form' elif isinstance(filter_field, (filters.RangeFilter, filters.NumericRangeFilter)): field_names = [f'{field_name}_min', f'{field_name}_max'] explode = None style = None else: field_names = [field_name] explode = None style = None return [ build_parameter_type(name=field_name, required=filter_field.extra['required'], location=OpenApiParameter.QUERY, description=description, schema=schema, explode=explode, style=style) for field_name in field_names ]
def resolve_filter_field(self, auto_schema, model, filterset_class, field_name, filter_field): from django_filters.rest_framework import filters unambiguous_mapping = { filters.CharFilter: OpenApiTypes.STR, filters.BooleanFilter: OpenApiTypes.BOOL, filters.DateFilter: OpenApiTypes.DATE, filters.DateTimeFilter: OpenApiTypes.DATETIME, filters.IsoDateTimeFilter: OpenApiTypes.DATETIME, filters.TimeFilter: OpenApiTypes.TIME, filters.UUIDFilter: OpenApiTypes.UUID, filters.DurationFilter: OpenApiTypes.DURATION, filters.OrderingFilter: OpenApiTypes.STR, filters.TimeRangeFilter: OpenApiTypes.TIME, filters.DateFromToRangeFilter: OpenApiTypes.DATE, filters.IsoDateTimeFromToRangeFilter: OpenApiTypes.DATETIME, filters.DateTimeFromToRangeFilter: OpenApiTypes.DATETIME, } if isinstance(filter_field, tuple(unambiguous_mapping)): for cls in filter_field.__class__.__mro__: if cls in unambiguous_mapping: schema = build_basic_type(unambiguous_mapping[cls]) break elif isinstance(filter_field, (filters.NumberFilter, filters.NumericRangeFilter)): # NumberField is underspecified by itself. try to find the # type that makes the most sense or default to generic NUMBER if filter_field.method: schema = self._build_filter_method_type( filterset_class, filter_field) if schema['type'] not in ['integer', 'number']: schema = build_basic_type(OpenApiTypes.NUMBER) else: model_field = self._get_model_field(filter_field, model) if isinstance(model_field, (models.IntegerField, models.AutoField)): schema = build_basic_type(OpenApiTypes.INT) elif isinstance(model_field, models.FloatField): schema = build_basic_type(OpenApiTypes.FLOAT) elif isinstance(model_field, models.DecimalField): schema = build_basic_type( OpenApiTypes.NUMBER) # TODO may be improved else: schema = build_basic_type(OpenApiTypes.NUMBER) elif filter_field.method: # try to make best effort on the given method schema = self._build_filter_method_type(filterset_class, filter_field) else: # last resort is to lookup the type via the model field. model_field = self._get_model_field(filter_field, model) if isinstance(model_field, models.Field): try: schema = auto_schema._map_model_field(model_field, direction=None) except Exception as exc: warn( f'Exception raised while trying resolve model field for django-filter ' f'field "{field_name}". Defaulting to string (Exception: {exc})' ) schema = build_basic_type(OpenApiTypes.STR) else: # default to string if nothing else works schema = build_basic_type(OpenApiTypes.STR) # primary keys are usually non-editable (readOnly=True) and map_model_field correctly # signals that attribute. however this does not apply in this context. schema.pop('readOnly', None) # enrich schema with additional info from filter_field enum = schema.pop('enum', None) if 'choices' in filter_field.extra: enum = [c for c, _ in filter_field.extra['choices']] if enum: schema['enum'] = sorted(enum, key=str) description = schema.pop('description', None) if filter_field.extra.get('help_text', None): description = filter_field.extra['help_text'] elif filter_field.label is not None: description = filter_field.label # parameter style variations based on filter base class if isinstance(filter_field, filters.BaseCSVFilter): schema = build_array_type(schema) field_names = [field_name] explode = False style = 'form' elif isinstance(filter_field, filters.MultipleChoiceFilter): schema = build_array_type(schema) field_names = [field_name] explode = True style = 'form' elif isinstance(filter_field, (filters.RangeFilter, filters.NumericRangeFilter)): try: suffixes = filter_field.field_class.widget.suffixes except AttributeError: suffixes = ['min', 'max'] field_names = [ f'{field_name}_{suffix}' if suffix else field_name for suffix in suffixes ] explode = None style = None else: field_names = [field_name] explode = None style = None return [ build_parameter_type(name=field_name, required=filter_field.extra['required'], location=OpenApiParameter.QUERY, description=description, schema=schema, explode=explode, style=style) for field_name in field_names ]
def parse(self, input_request, public): """ Iterate endpoints generating per method path operations. """ result = {} self._initialise_endpoints() endpoints = self._get_paths_and_endpoints( None if public else input_request) if spectacular_settings.SCHEMA_PATH_PREFIX is None: # estimate common path prefix if none was given. only use it if we encountered more # than one view to prevent emission of erroneous and unnecessary fallback names. non_trivial_prefix = len( set([view.__class__ for _, _, _, view in endpoints])) > 1 if non_trivial_prefix: path_prefix = os.path.commonpath( [path for path, _, _, _ in endpoints]) path_prefix = re.escape( path_prefix) # guard for RE special chars in path else: path_prefix = "/" else: path_prefix = spectacular_settings.SCHEMA_PATH_PREFIX if not path_prefix.startswith("^"): path_prefix = "^" + path_prefix # make sure regex only matches from the start # Adding plugin filter plugins = None # /pulp/api/v3/docs/api.json?plugin=pulp_file if input_request and "plugin" in input_request.query_params: plugins = [input_request.query_params["plugin"]] for path, path_regex, method, view in endpoints: plugin = view.__module__.split(".")[0] if plugins and plugin not in plugins: # plugin filter continue if not self.has_view_permissions(path, method, view): continue if input_request: request = input_request else: # mocked request to allow certain operations in get_queryset and get_serializer # without exceptions being raised due to no request. request = spectacular_settings.GET_MOCK_REQUEST( method, path, view, input_request) view.request = request schema = view.schema path = self.convert_endpoint_path_params(path, view, schema) # beware that every access to schema yields a fresh object (descriptor pattern) operation = schema.get_operation(path, path_regex, path_prefix, method, self.registry) # operation was manually removed via @extend_schema if not operation: continue # Removes html tags from OpenAPI schema if request is None or "include_html" not in request.query_params: operation["description"] = strip_tags(operation["description"]) # operationId as actions [list, read, sync, modify, create, delete, ...] if request and "bindings" in request.query_params: tokenized_path = schema._tokenize_path() tokenized_path = "_".join([ t.replace("-", "_").replace("/", "_").lower() for t in tokenized_path ]) action = schema.get_operation_id_action() if f"{tokenized_path}_{action}" == operation["operationId"]: operation["operationId"] = action # Adding query parameters if "parameters" in operation and schema.method.lower() == "get": fields_paramenter = build_parameter_type( name="fields", schema={"type": "string"}, location=OpenApiParameter.QUERY, description="A list of fields to include in the response.", ) operation["parameters"].append(fields_paramenter) not_fields_paramenter = build_parameter_type( name="exclude_fields", schema={"type": "string"}, location=OpenApiParameter.QUERY, description= "A list of fields to exclude from the response.", ) operation["parameters"].append(not_fields_paramenter) # Normalise path for any provided mount url. if path.startswith("/"): path = path[1:] if not path.startswith("{"): path = urljoin(self.url or "/", path) result.setdefault(path, {}) result[path][method.lower()] = operation return result
def parse(self, request, public): """ Iterate endpoints generating per method path operations. """ result = {} self._initialise_endpoints() # Adding plugin filter plugins = None # /pulp/api/v3/docs/api.json?plugin=pulp_file if request and "plugin" in request.query_params: plugins = [request.query_params["plugin"]] is_public = None if public else request for path, path_regex, method, view in self._get_paths_and_endpoints( is_public): plugin = view.__module__.split(".")[0] if plugins and plugin not in plugins: # plugin filter continue if not self.has_view_permissions(path, method, view): continue schema = view.schema path = self.convert_endpoint_path_params(path, view, schema) # beware that every access to schema yields a fresh object (descriptor pattern) operation = schema.get_operation(path, path_regex, method, self.registry) # operation was manually removed via @extend_schema if not operation: continue # Removes html tags from OpenAPI schema if request is None or "include_html" not in request.query_params: operation["description"] = strip_tags(operation["description"]) # operationId as actions [list, read, sync, modify, create, delete, ...] if request and "bindings" in request.query_params: tokenized_path = schema._tokenize_path() tokenized_path = "_".join([ t.replace("-", "_").replace("/", "_").lower() for t in tokenized_path ]) action = schema.get_operation_id_action() if f"{tokenized_path}_{action}" == operation["operationId"]: operation["operationId"] = action # Adding query parameters if "parameters" in operation and schema.method.lower() == "get": fields_paramenter = build_parameter_type( name="fields", schema={"type": "string"}, location=OpenApiParameter.QUERY, description="A list of fields to include in the response.", ) operation["parameters"].append(fields_paramenter) not_fields_paramenter = build_parameter_type( name="exclude_fields", schema={"type": "string"}, location=OpenApiParameter.QUERY, description= "A list of fields to exclude from the response.", ) operation["parameters"].append(not_fields_paramenter) # Normalise path for any provided mount url. if path.startswith("/"): path = path[1:] if not path.startswith("{"): path = urljoin(self.url or "/", path) result.setdefault(path, {}) result[path][method.lower()] = operation return result