def parse(self, request, public): """ Iterate endpoints generating per method path operations. """ result = {} self._initialise_endpoints() for path, path_regex, method, view in self._get_paths_and_endpoints( None if public else request): if not self.has_view_permissions(path, method, view): continue # mocked request to allow certain operations in get_queryset and get_serializer[_class] # without exceptions being raised due to no request. if not request: request = spectacular_settings.GET_MOCK_REQUEST( method, path, view, request) view.request = request if view.versioning_class and not is_versioning_supported( view.versioning_class): warn( f'using unsupported versioning class "{view.versioning_class}". view will be ' f'processed as unversioned view.') elif view.versioning_class: version = ( self.api_version # generator was explicitly versioned or getattr(request, 'version', None) # incoming request was versioned or view.versioning_class.default_version # fallback ) if not version: continue path = modify_for_versioning(self.inspector.patterns, method, path, view, version) if not operation_matches_version(view, version): continue assert isinstance(view.schema, AutoSchema), ( 'Incompatible AutoSchema used on View. Is DRF\'s DEFAULT_SCHEMA_CLASS ' 'pointing to "drf_spectacular.openapi.AutoSchema" or any other drf-spectacular ' 'compatible AutoSchema?') operation = view.schema.get_operation(path, path_regex, method, self.registry) # operation was manually removed via @extend_schema if not operation: continue # Normalise path for any provided mount url. if path.startswith('/'): path = path[1:] path = urljoin(self.url or '/', path) if spectacular_settings.CAMELIZE_NAMES: path, operation = camelize_operation(path, operation) result.setdefault(path, {}) result[path][method.lower()] = operation return result
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]) 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 for path, path_regex, method, view in endpoints: 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[_class] # without exceptions being raised due to no request. request = spectacular_settings.GET_MOCK_REQUEST( method, path, view, input_request) view.request = request if view.versioning_class and not is_versioning_supported( view.versioning_class): warn( f'using unsupported versioning class "{view.versioning_class}". view will be ' f'processed as unversioned view.') elif view.versioning_class: version = ( self.api_version # generator was explicitly versioned or getattr(request, 'version', None) # incoming request was versioned or view.versioning_class.default_version # fallback ) if not version: continue path = modify_for_versioning(self.inspector.patterns, method, path, view, version) if not operation_matches_version(view, version): continue assert isinstance(view.schema, AutoSchema), ( f'Incompatible AutoSchema used on View {view.__class__}. Is DRF\'s ' f'DEFAULT_SCHEMA_CLASS pointing to "drf_spectacular.openapi.AutoSchema" ' f'or any other drf-spectacular compatible AutoSchema?') with add_trace_message(getattr(view, '__class__', view).__name__): operation = view.schema.get_operation(path, path_regex, path_prefix, method, self.registry) # operation was manually removed via @extend_schema if not operation: continue if spectacular_settings.SCHEMA_PATH_PREFIX_TRIM: path = re.sub(pattern=path_prefix, repl='', string=path, flags=re.IGNORECASE) if not path.startswith('/'): path = '/' + path if spectacular_settings.CAMELIZE_NAMES: path, operation = camelize_operation(path, operation) result.setdefault(path, {}) result[path][method.lower()] = operation return result
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