""" Utility functions to translate Django REST Framework serializers into OpenAPI parameter objects. """ from typing import Any, List, Optional from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter from rest_framework import serializers def serializer_to_openapi_params( serializer_class: type[serializers.Serializer], location: str = 'query', exclude_fields: Optional[List[str]] = None, field_overrides: Optional[dict] = None, ) -> List[OpenApiParameter]: """ Convert a Django REST Framework serializer into a list of OpenAPI parameter objects. This provides more control than the built-in approach but requires more manual configuration. Use serializer_to_parameters() for most cases. Args: serializer_class: The DRF serializer class to convert location: The parameter location ('query', 'path', 'header', 'cookie') exclude_fields: List of field names to exclude from the parameters field_overrides: Dictionary to override specific field configurations Format: {'field_name': {'type': OpenApiTypes.STR, 'description': 'Custom desc'}} Returns: List of OpenApiParameter objects ready for use with extend_schema Example: from label_studio.projects.serializers import GetFieldsSerializer parameters = serializer_to_openapi_params( GetFieldsSerializer, location='query', field_overrides={ 'filter': { 'enum': ['all', 'pinned_only', 'exclude_pinned'], 'description': 'Filter type for the query' } } ) @extend_schema(parameters=parameters) def my_view(request): pass """ if exclude_fields is None: exclude_fields = [] if field_overrides is None: field_overrides = {} parameters = [] serializer_instance = serializer_class() for field_name, field in serializer_instance.fields.items(): if field_name in exclude_fields: continue # Get field configuration config = _get_field_config(field, field_overrides.get(field_name, {})) # Create OpenApiParameter param = OpenApiParameter( name=field_name, type=config['type'], location=location, required=config['required'], description=config['description'], default=config['default'], enum=config.get('enum'), **config.get('extra_kwargs', {}), ) parameters.append(param) return parameters def _get_field_config(field: serializers.Field, overrides: dict) -> dict: """ Extract configuration for a serializer field to create an OpenAPI parameter. Args: field: The DRF serializer field overrides: Any field-specific overrides Returns: Dictionary with OpenAPI parameter configuration """ # Start with base configuration config = { 'type': _map_field_type(field), 'required': getattr(field, 'required', False), 'description': _get_field_description(field), 'default': _get_field_default(field), 'extra_kwargs': {}, } # Apply overrides config.update(overrides) # Handle special field types if isinstance(field, serializers.ChoiceField): config['enum'] = [choice[0] if isinstance(choice, tuple) else choice for choice in field.choices] elif isinstance(field, serializers.BooleanField): config['type'] = OpenApiTypes.BOOL elif isinstance(field, (serializers.IntegerField, serializers.FloatField)): config['type'] = OpenApiTypes.INT if isinstance(field, serializers.IntegerField) else OpenApiTypes.NUMBER elif isinstance(field, serializers.DateTimeField): config['type'] = OpenApiTypes.STR config['extra_kwargs']['format'] = 'date-time' elif isinstance(field, serializers.DateField): config['type'] = OpenApiTypes.STR config['extra_kwargs']['format'] = 'date' elif isinstance(field, serializers.TimeField): config['type'] = OpenApiTypes.STR config['extra_kwargs']['format'] = 'time' elif isinstance(field, serializers.EmailField): config['type'] = OpenApiTypes.STR config['extra_kwargs']['format'] = 'email' elif isinstance(field, serializers.URLField): config['type'] = OpenApiTypes.STR config['extra_kwargs']['format'] = 'uri' elif isinstance(field, serializers.UUIDField): config['type'] = OpenApiTypes.STR config['extra_kwargs']['format'] = 'uuid' elif isinstance(field, serializers.JSONField): config['type'] = OpenApiTypes.OBJECT elif isinstance(field, serializers.ListField): config['type'] = OpenApiTypes.STR return config def _map_field_type(field: serializers.Field) -> str: """ Map DRF field types to OpenAPI types. Args: field: The DRF serializer field Returns: OpenAPI type string """ # Handle specific field types first if isinstance(field, serializers.BooleanField): return OpenApiTypes.BOOL elif isinstance(field, serializers.IntegerField): return OpenApiTypes.INT elif isinstance(field, serializers.FloatField): return OpenApiTypes.NUMBER elif isinstance(field, serializers.DecimalField): return OpenApiTypes.NUMBER elif isinstance(field, serializers.ListField): return OpenApiTypes.STR elif isinstance(field, serializers.JSONField): return OpenApiTypes.OBJECT elif isinstance(field, serializers.DictField): return OpenApiTypes.OBJECT else: # Default to string for most other field types return OpenApiTypes.STR def _get_field_description(field: serializers.Field) -> str: """ Extract description from a serializer field. Args: field: The DRF serializer field Returns: Field description or empty string """ if hasattr(field, 'help_text') and field.help_text: return str(field.help_text) elif hasattr(field, 'label') and field.label: return str(field.label) else: return '' def _get_field_default(field: serializers.Field) -> Any: """ Extract default value from a serializer field. Args: field: The DRF serializer field Returns: Default value or None """ if hasattr(field, 'default') and field.default is not None: if callable(field.default): # Skip callable defaults (like serializers.CreateOnlyDefault) return None return field.default return None