2025-10-30 11:48:34 +01:00

92 lines
2.5 KiB
Python

from abc import ABC, abstractmethod
from datetime import datetime
from json import loads
from typing import Any, Optional, Self
from pydantic import BaseModel
from pydantic.config import ExtraValues
from sqlalchemy import DateTime, func
from sqlmodel import Field, SQLModel
# --- Generic ---
class NdjsonMixin(ABC):
@abstractmethod
def _dump_json(self) -> str:
pass
def model_dump_ndjson(self) -> bytes:
return f"{self._dump_json()}\n".encode("utf-8")
# --- Pydantic ---
class NbeSchema(NdjsonMixin, BaseModel):
def _dump_json(self) -> str:
return self.model_dump_json()
class NbeSerializer(NbeSchema):
pass
# --- SQLModel ---
class NbeModel(NdjsonMixin, SQLModel):
def _dump_json(self) -> str:
return self.model_dump_json()
@classmethod
def model_validate_json(
cls,
json_data: str | bytes | bytearray,
*,
strict: bool | None = None,
extra: ExtraValues | None = None,
context: Any | None = None,
by_alias: bool | None = None,
by_name: bool | None = None,
) -> Self:
"""
Sourced from: https://github.com/fastapi/sqlmodel/discussions/852
Related:
- https://github.com/fastapi/sqlmodel/issues/453
- https://github.com/fastapi/sqlmodel/discussions/961
SQLModel's `model_validate_json` is broken on `table=True`, when using JSON columns linked to a Pydantic model.
Nested fields defined this way are transformed to plain dict/list instead of their respective Pydantic models.
Because `model_validate` has its behaviour fixed, we delegate to it.
Note: `pydantic.TypeAdapter` also suffers from this issue.
"""
python_data = loads(json_data)
return cls.model_validate(obj=python_data, strict=strict, context=context)
class IdMixin:
id: Optional[int] = Field(default=None, primary_key=True)
class TimestampedMixin:
created_at: datetime | None = Field(
default=None,
sa_type=DateTime(timezone=True), # type: ignore[arg-type]
sa_column_kwargs={"server_default": func.now(), "nullable": False},
)
updated_at: datetime | None = Field(
default=None,
sa_type=DateTime(timezone=True), # type: ignore[arg-type]
sa_column_kwargs={"server_default": func.now(), "onupdate": func.now(), "nullable": False},
)
class IdNbeModel(NbeModel, IdMixin):
pass
class TimestampedModel(IdNbeModel, TimestampedMixin):
pass