2025-10-18 05:44:36 +00:00
|
|
|
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2025-10-19 09:24:23 +00:00
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
2025-10-18 03:26:42 +00:00
|
|
|
|
2025-07-28 23:23:09 +00:00
|
|
|
|
2025-10-18 05:44:36 +00:00
|
|
|
def to_camel(s: str) -> str:
|
|
|
|
|
parts = s.split("_")
|
|
|
|
|
return parts[0] + "".join(p.title() for p in parts[1:])
|
2025-07-28 23:23:09 +00:00
|
|
|
|
2025-10-18 05:44:36 +00:00
|
|
|
|
|
|
|
|
class ApiModel(BaseModel):
|
|
|
|
|
model_config = ConfigDict(
|
|
|
|
|
alias_generator=to_camel,
|
|
|
|
|
populate_by_name=True,
|
|
|
|
|
ser_json_inf_nan="null",
|
|
|
|
|
arbitrary_types_allowed=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseLinkedModel(ApiModel):
|
2025-07-28 23:23:09 +00:00
|
|
|
@model_validator(mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def auto_populate_ids(cls, data: dict[str, Any]) -> dict[str, Any]:
|
|
|
|
|
if isinstance(data, dict):
|
|
|
|
|
for key, value in data.copy().items():
|
|
|
|
|
if not key.endswith("_id") and hasattr(value, "id") and value is not None:
|
|
|
|
|
id_key = key + "_id"
|
|
|
|
|
|
|
|
|
|
if id_key in data:
|
|
|
|
|
# If the id_key already exists, ensure it matches the value's id
|
|
|
|
|
if data[id_key] != value.id:
|
2025-10-18 05:44:36 +00:00
|
|
|
raise ValueError(
|
|
|
|
|
f"ID mismatch for {key}: {data[id_key]} != {value.id}"
|
|
|
|
|
)
|
2025-07-28 23:23:09 +00:00
|
|
|
else:
|
|
|
|
|
# If the id_key does not exist, set it to the value's id
|
|
|
|
|
data[id_key] = value.id
|
|
|
|
|
|
2025-10-18 03:26:42 +00:00
|
|
|
return data
|
2025-10-18 05:44:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProblemDetails(ApiModel):
|
|
|
|
|
type: str = Field(default="about:blank")
|
|
|
|
|
title: str
|
|
|
|
|
status: int
|
|
|
|
|
detail: Optional[str] = None
|
|
|
|
|
instance: Optional[str] = None
|
|
|
|
|
errors: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Page(ApiModel, Generic[T]):
|
|
|
|
|
items: List[T]
|
|
|
|
|
next_cursor: Optional[str] = Field(default=None, alias="nextCursor")
|
|
|
|
|
prev_cursor: Optional[str] = Field(default=None, alias="prevCursor")
|
|
|
|
|
total: Optional[int] = Field(default=None, description="Optional total count")
|