Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
 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'},
         )
     ]
Ejemplo n.º 8
0
 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
Ejemplo n.º 9
0
 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"},
         )
     ]
Ejemplo n.º 10
0
 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."),
         )
     ]
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
    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
        ]
Ejemplo n.º 15
0
    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)
Ejemplo n.º 16
0
    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", [])
        ]
Ejemplo n.º 17
0
    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"},
            )
        ]
Ejemplo n.º 18
0
    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
Ejemplo n.º 19
0
    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
        ]
Ejemplo n.º 21
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
Ejemplo n.º 22
0
    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