pythonpython-3.xpydantic

Alter field after instantiation in Pydantic BaseModel class


With a Pydantic class as follows, I want to transform the foo field by applying a replace operation:

from typing import List
from pydantic import BaseModel

class MyModel(BaseModel):
    foo: List[str]

my_object = MyModel(foo="hello-there")
my_object.foo = [s.replace("-", "_") for s in my_object.foo]

How can I do the replace operation right within the class, when the object is created? Without Pydantic I would simply do that within __init(self, foo) but since Pydantic creates its own __init__ implementation I'm not sure how to proceed exactly.


Solution

  • Using a pydantic BaseModel in version 2.0+

    Here we have the __post_model_init__ dunder method at our disposal to work with the object after instantiation.

    from typing import List
    from pydantic import BaseModel
    
    class MyModel(BaseModel):
        foo: List[str]
    
        def model_post_init(self, __context):
            self.foo = [s.replace("-", "_") for s in self.foo]
    
    my_object = MyModel(foo=["hello-there"])
    
    print(my_object)
    # Outputs foo=['hello_there']
    

    Using a pydantic BaseModel in version <2.0

    It seems as you would have to override the basemodels init method, something like this:

    from typing import List
    from pydantic import BaseModel
    
    class MyModel(BaseModel):
        foo: List[str]
    
        def __init__(self, **data):
            data["foo"] = [s.replace("-", "_") for s in data["foo"]]
            super().__init__(**data)
    
    my_object = MyModel(foo=["hello-there"])
    
    print(my_object)
    # Outputs foo=['hello_there']
    

    Using a Pydantic dataclass

    ... or you could also turn it into a pydantic dataclass and use the post init dunder provided by pydantic to do other things upon instantiation. e.g:

    from typing import List
    from pydantic.dataclasses import dataclass
    
    @dataclass
    class MyModel():
        foo: List[str]
    
        def __post_init__(self):
            self.foo = [s.replace("-", "_") for s in self.foo]
    
    my_object = MyModel(foo=["hello-there"])
    
    print(my_object)
    
    # Outputs foo=['hello_there']
    

    Comments on @validator and @field_validator

    I would personally avoid using the @validator or @field_validator decorators to mutate the object purely based on their naming. Their namings suggests they are meant to be used for validating the object so I would not expect any mutation to happen if i saw it in code.