Search code examples
pythonpydantic

Return all extra passed to pydantic model


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"]

Solution

  • 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!