importsysfromtypingimportTYPE_CHECKING,List,Type,UnionimportpydanticfrompydanticimportBaseModel,ConfigDict,RootModelfromtortoiseimportfieldsifsys.version_info>=(3,11):# pragma: nocoveragefromtypingimportSelfelse:fromtyping_extensionsimportSelfifTYPE_CHECKING:# pragma: nocoveragefromtortoise.modelsimportModelfromtortoise.querysetimportQuerySet,QuerySetSingledef_get_fetch_fields(pydantic_class:"Type[PydanticModel]",model_class:"Type[Model]")->List[str]:""" Recursively collect fields needed to fetch :param pydantic_class: The pydantic model class :param model_class: The tortoise model class :return: The list of fields to be fetched """fetch_fields=[]forfield_name,field_typeinpydantic_class.__annotations__.items():origin=getattr(field_type,"__origin__",None)iforiginin(list,List,Union):field_type=field_type.__args__[0]# noinspection PyProtectedMemberiffield_nameinmodel_class._meta.fetch_fieldsandissubclass(field_type,PydanticModel):subclass_fetch_fields=_get_fetch_fields(field_type,field_type.model_config["orig_model"])ifsubclass_fetch_fields:fetch_fields.extend([field_name+"__"+fforfinsubclass_fetch_fields])else:fetch_fields.append(field_name)returnfetch_fields
[docs]classPydanticModel(BaseModel):""" Pydantic BaseModel for Tortoise objects. This provides an extra method above the usual Pydantic `model properties <https://docs.pydantic.dev/latest/usage/models/#model-properties>`__ """model_config=ConfigDict(from_attributes=True)# noinspection PyMethodParameters@pydantic.field_validator("*")# It is a classmethod!def_tortoise_convert(cls,value):# pylint: disable=E0213# Computed fieldsifcallable(value):returnvalue()# Convert ManyToManyRelation to listifisinstance(value,(fields.ManyToManyRelation,fields.ReverseRelation)):returnlist(value)returnvalue
[docs]@classmethodasyncdeffrom_tortoise_orm(cls,obj:"Model")->Self:""" Returns a serializable pydantic model instance built from the provided model instance. .. note:: This will prefetch all the relations automatically. It is probably what you want. If you don't want this, or require a ``sync`` method, look to using ``.from_orm()``. In that case you'd have to manage prefetching yourself, or exclude relational fields from being part of the model using :class:`tortoise.contrib.pydantic.creator.PydanticMeta`, or you would be getting ``OperationalError`` exceptions. This is due to how the ``asyncio`` framework forces I/O to happen in explicit ``await`` statements. Hence we can only do lazy-fetching during an awaited method. :param obj: The Model instance you want serialized. """# Get fields needed to fetchfetch_fields=_get_fetch_fields(cls,cls.model_config["orig_model"])# type: ignore# Fetch fieldsawaitobj.fetch_related(*fetch_fields)returncls.model_validate(obj)
[docs]@classmethodasyncdeffrom_queryset_single(cls,queryset:"QuerySetSingle")->Self:""" Returns a serializable pydantic model instance for a single model from the provided queryset. This will prefetch all the relations automatically. :param queryset: a queryset on the model this PydanticModel is based on. """fetch_fields=_get_fetch_fields(cls,cls.model_config["orig_model"])# type: ignorereturncls.model_validate(awaitqueryset.prefetch_related(*fetch_fields))
[docs]@classmethodasyncdeffrom_queryset(cls,queryset:"QuerySet")->List[Self]:""" Returns a serializable pydantic model instance that contains a list of models, from the provided queryset. This will prefetch all the relations automatically. :param queryset: a queryset on the model this PydanticModel is based on. """fetch_fields=_get_fetch_fields(cls,cls.model_config["orig_model"])# type: ignorereturn[cls.model_validate(e)foreinawaitqueryset.prefetch_related(*fetch_fields)]
[docs]classPydanticListModel(RootModel):""" Pydantic BaseModel for List of Tortoise Models This provides an extra method above the usual Pydantic `model properties <https://pydantic-docs.helpmanual.io/usage/models/#model-properties>`__ """
[docs]@classmethodasyncdeffrom_queryset(cls,queryset:"QuerySet")->Self:""" Returns a serializable pydantic model instance that contains a list of models, from the provided queryset. This will prefetch all the relations automatically. :param queryset: a queryset on the model this PydanticListModel is based on. """submodel=cls.model_config["submodel"]# type: ignorefetch_fields=_get_fetch_fields(submodel,submodel.model_config["orig_model"])returncls.model_validate([submodel.model_validate(e)foreinawaitqueryset.prefetch_related(*fetch_fields)])