I created a pydantic model that accepts extra fields and store them in an internal dict.
The model has a validator to compare these extra fields with the model attributes, and if they differ it populates a updated_fields attribute.
So far so good but I have just realized that the comparison doesn't take into account the fields type.
Given that I pass everything in as strings from an object, the attributes are converted to the correct type but the extra fields are not.
So when I compare I might compare the same values but having different types, leading to errors.
In practise:
from typing import Optional, List, Dict, Union
from pydantic import BaseModel, root_validator
class MyModel(BaseModel):
my_field_str: str = ""
my_field_int: int = ""
another_field_float: Optional[float] = 0.0
updated_fields: List[str] = []
extra: Dict[str, Union[str, int, float]]
class Config:
"""config class."""
allow_population_by_field_name = True
orm_mode = True
@root_validator(pre=True)
@classmethod
def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]: # type: ignore
"""Store extra fields passed to the model into an internal dict."""
all_required_field_names = {
field.alias for field in cls.__fields__.values() if field.alias != "extra"
} # to support alias
extra: Dict[str, Any] = {} # type: ignore
for field_name in list(values):
if field_name not in all_required_field_names:
extra[field_name] = values.pop(field_name)
values["extra"] = extra
return values
@staticmethod
def _populate_updated_fields(values: dict[str, Any]) -> list[str]: # type: ignore
"""Compute which fileds were updated."""
updated_fields = []
excluded_fields = ["extra", "updated_fields"]
for k, v in values.items():
if v is not None and k not in excluded_fields and v != values["extra"].get("orig_" + k, None):
updated_fields.append(k) # PROBLEM: cast type of the field
return updated_fields
@root_validator()
@classmethod
def populate_updated_fields(cls, values: dict[str, Any]) -> dict[str, Any]: # type: ignore
values["updated_fields"] = cls._populate_updated_fields(values)
return values
my_obj = MyModel.parse_obj({
'my_field_str': 'hello',
'my_field_int': '25',
'another_field_float': '47.5',
'orig_my_field_str': 'hello',
'orig_my_field_int': '25',
'orig_another_field_float': '47.5',
})
How can I perform the correct comparison taking into account the types? I thought about:
- Introspect the class and check what type is the field being compared to and convert the extra field on the fly (how?)
- Already convert the extra values when creating the object (not being model attributes I wouldn't know how to specify the type)