"""Schemas.""" from typing import Annotated, List, Literal, Optional, TypeVar from pydantic import BaseModel, BeforeValidator, ConfigDict _T = TypeVar("_T") def _split_comma_separated(value): """Split comma-separated query values into a flat list of strings. flask-pydantic reads list query params via ``to_dict(flat=False)`` (the ``getlist`` behaviour), so ``?x=a,b,c`` arrives as ``["a,b,c"]`` while ``?x=a&x=b`` arrives as ``["a", "b"]``. Normalise both — and a bare string — into ``["a", "b", "c"]``. ``None`` passes through so the field stays optional. This only splits into strings; pydantic coerces each element to the parametrised type afterwards (a ``BeforeValidator`` runs before validation), so ``CommaSeparatedList[int]`` turns ``"1,2"`` into ``[1, 2]``. """ if value is None: return None if isinstance(value, str): value = [value] return [item for entry in value for item in str(entry).split(",")] # Generic field type for comma-separated query parameters. Parametrise with the # element type, e.g. ``Optional[CommaSeparatedList[str]]`` or # ``Optional[CommaSeparatedList[int]]``. CommaSeparatedList = Annotated[List[_T], BeforeValidator(_split_comma_separated)] class BaseSchema(BaseModel): """Base pydantic schema.""" @classmethod def parse(cls, obj): """Validate and create an instance of the schema.""" return cls.model_validate(obj) def dump(self, **kwargs): """Dump a pydantic instance to JSON.""" return self.model_dump(mode="json", **kwargs) class BaseTableSchema(BaseSchema): """Base schema for models representing ORM tables.""" model_config = ConfigDict(from_attributes=True, populate_by_name=True) class PaginatedRequestMixin: """Paginated request mixin.""" limit: Optional[int] = 10 offset: Optional[int] = 0 class SortableRequestMixin: """Sortable request mixin.""" sort_key: Optional[str] = None sort_direction: Optional[Literal["ASC", "DESC"]] = "ASC"