Search code examples
pythonpydantic

Pydantic: Define a default field as a function of another


I'm using pydantic dataclasses to create a non-mutable dataclass that contains a name, a label and an id. I want to be able to specify the label when creating a MyClass object or give a default label that depends on the value of name.

The current implementation only works when label is part of the input arguments is the following:

@dataclass(frozen=True)
class MyClass
    name: str = Field(default="class_name")
    label: str = Field(default=None)
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))

    _validate_uuid = field_validator("id")(uuid_error)

    @field_validator("label")
    def set_default_label(cls, v, values):
        if v is None:
            v = foo(values["name"])
        return v
    ...

Using this code, we can trigger two different behaviours:

MyClass(name="object_name", label=None)

returns the right object: MyClass(name="object_name", label=foo("object_name"),...)

but if we rely on the default value for label, e.g. using this:

MyClass(name="object_name")

then the validator is not called and label stays None:

MyClass(name="object_name", label=None)


Solution

  • Without additional configuration default values are not validated in Pydantic. So you have to pass the corresponding config option to the definition of the data class. Check out the following example:

    from pydantic import Field, field_validator, ConfigDict
    import uuid
    from pydantic.dataclasses import dataclass
    
    def foo(name):
        return name + "_label"
    
    
    @dataclass(config=ConfigDict(validate_default=True))
    class MyClass:
        name: str = Field(default="class_name")
        label: str = Field(default=None)
        id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    
        @field_validator("label", mode="before")
        @classmethod
        def set_default_label(cls, v, info):
            if v is None:
                v = foo(info.data["name"])
            return v
    
    print(MyClass())
    

    Which prints:

    MyClass(name='class_name', label='class_name_label', id='fda6cbe7-a756-4e93-9924-0cd024f59251')
    

    In addition I think it is good to be explicit about the validation mode, and declaring the validation as a class method.

    I hope this helps!