odmantic
odmantic copied to clipboard
Odmantic Does Not Add Pydantic Computed Fields to MongoDB
Bug
A clear and concise description of what the bug is.
Pydantic has a nice feature around computed fields that allow you to create an attribute based on logic in other fields, however these fields are not added to Model.__odm_fields__
which means they don't get used when running engine.save(Model)
If you add a ODMBaseField to Model.__odm_fields__
it will include it, but this is very hacky and shouldn't be the right way to do this.
Current Behavior
class ExampleModel(Model):
data_from_user: str
@computed_field
def computed_data(self)->str:
return somefunction(self.data_from_user)
item = ExampleModel.model_validate({"data_from_user":"foo"})
item.model_dump_doc() == {"data_from_user":"foo", "computed_data":"bar"}
engine.save(item)
then the data in the collection is
{"data_from_user":"foo"}
Expected behavior
class ExampleModel(Model):
data_from_user: str
@computed_field
def computed_data(self)->str:
return somefunction(self.data_from_user)
item = ExampleModel.model_validate({"data_from_user":"foo"})
item.model_dump_doc() == {"data_from_user":"foo", "computed_data":"bar"}
engine.save(item)
then the data in the collection would be
{"data_from_user":"foo", "computed_data":"bar"}
Environment
- ODMantic version: 1.0.0
- MongoDB version: 6.0.13
- Pydantic infos (output of
python -c "import pydantic.utils; print(pydantic.utils.version_info())
): pydantic version: 2.5.3 pydantic-core version: 2.14.6 pydantic-core build: profile=release pgo=true install path: /home/lucastieman/PycharmProjects/monorepo/venv/lib/python3.11/site-packages/pydantic python version: 3.11.6 (main, Oct 3 2023, 00:00:00) [GCC 13.2.1 20230728 (Red Hat 13.2.1-1)] platform: Linux-6.6.3-100.fc38.x86_64-x86_64-with-glibc2.37 related packages: typing_extensions-4.8.0 fastapi-0.104.1 email-validator-2.1.0.post1
I'd propose the following change to the engine._save method
async def _save(
self, instance: ModelType, session: "AsyncIOMotorClientSession"
) -> ModelType:
"""Perform an atomic save operation in the specified session"""
for ref_field_name in instance.__references__:
sub_instance = cast(Model, getattr(instance, ref_field_name))
await self._save(sub_instance, session)
fields_to_update = instance.__fields_modified__ | instance.__mutable_fields__ | {ODMBaseField(key,instance.model_config) for key, value in instance.model_computed_fields.items() if value.repr}
if len(fields_to_update) > 0:
doc = instance.model_dump_doc(include=fields_to_update)
collection = self.get_collection(type(instance))
try:
await collection.update_one(
instance.model_dump_doc(include={instance.__primary_field__}),
{"$set": doc},
upsert=True,
session=session,
)
except pymongo.errors.DuplicateKeyError as e:
raise DuplicateKeyError(instance, e)
object.__setattr__(instance, "__fields_modified__", set())
return instance
I support this change, I am facing the same issue.