from bson import ObjectId from pydantic import BaseModel, Field, field_validator, model_serializer from pydantic_core import core_schema from typing import Any class PyObjectId(str): """Custom type that validates and serialises MongoDB ObjectIds.""" @classmethod def __get_validators__(cls): yield cls.validate @classmethod def validate(cls, v: Any) -> "PyObjectId": if isinstance(v, ObjectId): return cls(str(v)) if isinstance(v, str): if not ObjectId.is_valid(v): raise ValueError(f"Invalid ObjectId: {v!r}") return cls(v) raise ValueError(f"Cannot convert {type(v)} to ObjectId") @classmethod def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> Any: return core_schema.no_info_plain_validator_function( cls.validate, serialization=core_schema.to_string_ser_schema(), ) def mongo_doc_to_dict(doc: dict) -> dict: """Convert MongoDB document: stringify _id and remove internal keys.""" if doc is None: return {} result = {} for k, v in doc.items(): if k == "_id": result["id"] = str(v) elif isinstance(v, ObjectId): result[k] = str(v) else: result[k] = v return result