Alter field after instantiation in Pydantic BaseModel class

Question:

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.

Asked By: Jivan

||

Answers:

Using a pydantic BaseModel

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']
Answered By: tbjorch

Use the @validator decorator (in this case with the option each_item=True, since we want the validator to look at each item in the list rather than the list itself):

from typing import List
from pydantic import BaseModel, validator

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

    @validator('foo', each_item=True)
    def replace_hyphen(cls,v):
        return v.replace("-","_")

my_object = MyModel(foo=["hello-there"])

print(my_object)
# Outputs foo=['hello_there']

or if you prefer dataclasses:

from typing import List
from pydantic import validator
from pydantic.dataclasses import dataclass

@dataclass
class MyModelDC():
    foo: List[str]

    @validator('foo', each_item=True)
    def replace_hyphen(cls,v):
        return v.replace("-","_")

my_objectDC = MyModelDC(foo=["hello-there"])

print(my_objectDC)
# Outputs foo=['hello_there']
Answered By: Sacha

Pydantic version 2.0+ (unreleased as of now)

You can do:

class MyModel(BaseModel):
    a: int
    b: str
    
    def model_post_init(self, **kwargs) -> None:
        values = self.dict()
        # manipulation on fields

It is not yet documented but here is the test:
https://github.com/pydantic/pydantic/blob/594effa279668bd955e98f1cd5c036b37d3bbd40/tests/test_main.py#L1828-L1850

And here is the reference:
https://github.com/pydantic/pydantic/issues/1729#issuecomment-1300576214

Answered By: Itamar Podjarski
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.