Created
August 5, 2026 01:49
-
-
Save c6401/e117ef4792570dd16e5746edd426b3c7 to your computer and use it in GitHub Desktop.
tmp pydantic sort
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import re | |
| from enum import Enum | |
| from typing import Annotated, Any | |
| from fastapi import FastAPI, Query | |
| from pydantic import BaseModel, field_validator | |
| _CAMEL_BOUNDARY = re.compile(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])") | |
| # lowerCamelCase only: no underscores, no leading capital | |
| _CAMEL_SHAPE = re.compile(r"^[a-z][a-zA-Z0-9]*$") | |
| def camel_to_snake(value: str) -> str: | |
| return _CAMEL_BOUNDARY.sub("_", value).lower() | |
| def snake_to_camel(value: str) -> str: | |
| head, *tail = value.split("_") | |
| return head + "".join(word.capitalize() for word in tail) | |
| def parse_sort( | |
| value: Any, | |
| allowed: type[Enum], | |
| *, | |
| strict_camel_case: bool = True, | |
| ) -> list[str]: | |
| """Turn a `sort` query param into a list of snake_case field names. | |
| Accepts `"a,b"`, `["a,b"]`, or `["a", "b"]` (FastAPI hands over a list). | |
| A leading "-" marks descending order and is preserved on the output. | |
| Names not present in `allowed` raise ValueError -> HTTP 422. | |
| """ | |
| if value is None: | |
| return [] | |
| allowed_values = {str(f.value) for f in allowed} | |
| items = [value] if isinstance(value, str) else value | |
| parsed: list[str] = [] | |
| unknown: list[str] = [] | |
| for chunk in items: | |
| for raw in str(chunk).split(","): | |
| raw = raw.strip() | |
| if not raw: | |
| continue | |
| desc = raw.startswith("-") | |
| if desc: | |
| raw = raw[1:] | |
| if strict_camel_case and not _CAMEL_SHAPE.match(raw): | |
| unknown.append(raw) | |
| continue | |
| field = camel_to_snake(raw) | |
| if field not in allowed_values: | |
| unknown.append(raw) | |
| continue | |
| parsed.append(f"-{field}" if desc else field) | |
| if unknown: | |
| allowed_camel = ", ".join(snake_to_camel(str(f.value)) for f in allowed) | |
| raise ValueError( | |
| f"Unknown sort field(s): {', '.join(unknown)}. Allowed: {allowed_camel}" | |
| ) | |
| return parsed | |
| # --- one enum + one model per resource --------------------------------------- | |
| class BookSortField(str, Enum): | |
| NAME = "name" | |
| PUBLISHED_YEAR = "published_year" | |
| CREATED_AT = "created_at" | |
| AUTHOR_FULL_NAME = "author_full_name" | |
| BOOK_ISBN = "book_isbn" | |
| class UserSortField(str, Enum): | |
| EMAIL = "email" | |
| LAST_LOGIN_AT = "last_login_at" | |
| SIGNUP_DATE = "signup_date" | |
| class BookSortParams(BaseModel): | |
| sort: list[str] = [] | |
| @field_validator("sort", mode="before") | |
| @classmethod | |
| def _parse_sort(cls, value: Any) -> list[str]: | |
| return parse_sort(value, BookSortField) | |
| class UserSortParams(BaseModel): | |
| sort: list[str] = [] | |
| @field_validator("sort", mode="before") | |
| @classmethod | |
| def _parse_sort(cls, value: Any) -> list[str]: | |
| return parse_sort(value, UserSortField) | |
| app = FastAPI() | |
| @app.get("/books") | |
| async def list_books(params: Annotated[BookSortParams, Query()]): | |
| return {"sort": params.sort} | |
| @app.get("/users") | |
| async def list_users(params: Annotated[UserSortParams, Query()]): | |
| return {"sort": params.sort} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment