Pydantic#

Private Attributes#

from typing import Optional
from pydantic import BaseModel, ConfigDict
from datetime import datetime


class TodoItem(BaseModel):
    content: str
    _is_completed: bool = False
    _created_at: datetime = datetime.now()
    _completed_at: Optional[datetime] = None

    model_config = ConfigDict(
        frozen=True,
    )

    @property
    def is_completed(self):
        return self._is_completed

    def complete(self) -> None:
        self._is_completed = True
        self._completed_at = datetime.now()

    def spent_time(self) -> Optional[datetime]:
        if self._completed_at is None:
            return None
        else:
            return self._completed_at - self._created_at


item = TodoItem(content="Do homework")
print(item.spent_time())
item.complete()
print(item.spent_time())
None
0:00:00.001064
item._is_completed = True
from typing import Optional
from pydantic import BaseModel, PrivateAttr
from datetime import datetime


class TodoItem(BaseModel):
    content: str
    _is_completed: bool = PrivateAttr(default=False)
    _created_at: datetime = PrivateAttr(default_factory=datetime.now)
    _completed_at: Optional[datetime] = PrivateAttr(default=None)

    @property
    def is_completed(self):
        return self._is_completed

    def complete(self) -> None:
        self._is_completed = True
        self._completed_at = datetime.now()

    def spent_time(self) -> Optional[datetime]:
        if self._completed_at is None:
            return None
        else:
            return self._completed_at - self._created_at


item = TodoItem(content="Do homework")
print(item.spent_time())
item.complete()
print(item.spent_time())
None
0:00:00.000242
item._is_completed = False
item.model_dump()
{'content': 'Do homework'}
item._is_completed = False
item._is_completed
False

Class Attributes#

Class attributes can be defined with the type annotation ClassVar.

from typing import ClassVar
from pydantic import BaseModel


class NameFormatter:
    def __init__(self, capitalized: bool = False) -> None:
        self._capitalized = capitalized

    def format(self, name: str) -> str:
        name = name.strip().title()

        if self._capitalized:
            name = name.upper()

        return name


class User(BaseModel):
    name_formatter: ClassVar[NameFormatter]

    name: str
    age: int

    @property
    def formatted_name(self) -> str:
        return self.name_formatter.format(self.name)


user = User(name="isaac fei", age=23)

user
User(name='isaac fei', age=23)
User.name_formatter = NameFormatter(capitalized=True)
user.formatted_name
'ISAAC FEI'

Post Initialization#

Decorating with model_validator#

from pydantic import BaseModel, model_validator


class User(BaseModel):
    name: str
    age: int

    @model_validator(mode="after")
    def init_lucky_number(self) -> None:
        # Create a private attribute
        # using initialized attributes
        self._lucky_number = hash(self.name)

    @property
    def lucky_number(self) -> int:
        return self._lucky_number

Note that we defined self._lucky_number as a private attribute with an underscore prefix. If we use self.lucky_number, an exception will be triggered due to a violation of Pydantic’s validation rules.

To expose the value of self._lucky_number, we can make it a property.

user = User(name="Isaac", age=23)

user.lucky_number
-662702122252289913

Overriding model_post_init#

Overriding the BaseModel’s method model_post_init is the preferred way of conducting the post initialization of the instance.

from typing import Any
from pydantic import BaseModel


class User(BaseModel):
    name: str
    age: int

    def model_post_init(self, __context: Any) -> None:
        # Call method of super class
        super().model_post_init(__context)

        # Create a private attribute
        # using initialized attributes
        self._lucky_number = hash(self.name)

    @property
    def lucky_number(self) -> int:
        return self._lucky_number
user = User(name="Isaac", age=23)

user.lucky_number
-662702122252289913