Esempio n. 1
0
    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
Esempio n. 2
0
    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
Esempio n. 3
0
    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