I'm trying to get a list of all extra fields not defined in the schema. I saw solution posted here but it ignores any nested models. Optimal solution would create a variable in the Pydantic model with extras that I could access after new object with passed data is created but not sure if this is even possible.
Here is the code I'm working with.
Edit: I want .extras
to be something like a property that returns not just the extra data directly on that instance, but also the extra data on any nested model instances it holds
from typing import Any, Dict, Optional
from pydantic import BaseModel
from pydantic import BaseModel, Field, root_validator
unnecessary_data = {
"name": "Lévy",
"age": 3,
"key_parent": "value", # unnecessary
"key2_parent": "value2", # unnecessary x2
"address": {
"city": "Wonderland",
"zip_code": "ABCDE",
"number": 123,
"key_child": 1232 # unnecessary x
}
}
class NewBase(BaseModel):
versio: Optional[str] = Field(alias='version') # just to show that it supports alias too
extra: Dict[str, Any]
@root_validator(pre=True)
def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]:
all_required_field_names = {field.alias for field in cls.__fields__.values() if field.alias != 'extra'} # to support alias
extra: Dict[str, Any] = {}
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
class Address(NewBase):
"""
Cat API Address definition
"""
city: str
zip_code: str
number: int
class CatRequest(NewBase):
"""
Cat API Request definition
"""
name: str
age: int
address: Address
validated = CatRequest(**unnecessary_data)
print(validated.extras)
>> ["key_parent", "key2_parent", "address.key_child"]
Here is a version updated to Pydantic v2:
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field, model_validator
unnecessary_data = {
"name": "Lévy",
"age": 3,
"key_parent": "value", # unnecessary
"key2_parent": "value2", # unnecessary x2
"address": {
"city": "Wonderland",
"zip_code": "ABCDE",
"number": 123,
"key_child": 1232 # unnecessary x
}
}
class NewBase(BaseModel):
versio: Optional[str] = Field(alias="version", default=None)
extra: Dict[str, Any] = Field(default={}, exclude=True)
@model_validator(mode="before")
@classmethod
def validator(cls, values):
extra, valid = {}, {}
for key, value in values.items():
if key in cls.model_fields:
valid[key] = value
else:
extra[key] = value
valid["extra"] = extra
return valid
@property
def extra_flat(self):
extra_flat = {**self.extra}
for name, value in self:
if isinstance(value, NewBase) and value.extra:
data = {f"{name}.{k}": v for k, v in value.extra_flat.items()}
extra_flat.update(data)
return extra_flat
class Address(NewBase):
"""
Cat API Address definition
"""
city: str
zip_code: str
number: int
class CatRequest(NewBase):
"""
Cat API Request definition
"""
name: str
age: int
address: Address
validated = CatRequest(**unnecessary_data)
print(validated.extra_flat)
Which prints:
{'key_parent': 'value', 'key2_parent': 'value2', 'address.key_child': 1232}
I hope this helps!