Skip to content

Instantly share code, notes, and snippets.

@kubosuke
Last active January 29, 2024 08:20
Show Gist options
  • Save kubosuke/287024876ee9d2a17b17ab7db67260d5 to your computer and use it in GitHub Desktop.
Save kubosuke/287024876ee9d2a17b17ab7db67260d5 to your computer and use it in GitHub Desktop.
pydantic.py

Static typing

Able to raise error when the args does not match with predefined type

# from dataclasses import dataclass
from pydantic.dataclasses import dataclass


@dataclass
class Company:
    corporate_id: int
    name: str
    country_id: int


good_company = Company(corporate_id=1, name="熊本株式会社", country_id=81)
print(good_company.corporate_id, good_company.name, good_company.country_id)  # debug


# pydantic.error_wrappers.ValidationError: 1 validation error for Company corporate_id value is not a valid integer (type=type_error.integer)
bad_company1 = Company(corporate_id="not int", name="鹿児島株式会社", country_id=81)

pydantic is primarily a parsing library, not a validation library. Validation is a means to an end: building a model which conforms to the types and constraints provided. In other words, pydantic guarantees the types and constraints of the output model, not the input data. https://pydantic-docs.helpmanual.io/usage/models/#data-conversion

It means, when the args is castable to the predefined type, it won't raise error.

from pydantic.dataclasses import dataclass


@dataclass
class Company:
    corporate_id: int
    name: str
    country_id: int


# 勝手にcast出来るものはcastされる
bad_company2 = Company(corporate_id=2.1, name=3.14, country_id="2")
print(bad_company2.corporate_id, type(bad_company2.corporate_id)) # 2 <class 'int'>
print(bad_company2.name, type(bad_company2.name)) # 3.14 <class 'str'>
print(bad_company2.country_id, type(bad_company2.country_id)) # 2 <class 'int'>

JSONize

import dataclasses
import json

from pydantic.dataclasses import dataclass
from pydantic.json import pydantic_encoder


@dataclass
class User:
    id: int
    name: str = "John Doe"
    friends: list[int] = dataclasses.field(default_factory=lambda: [0])


user = User(id="42")
# JSON化
user_json = json.dumps(user, indent=4, default=pydantic_encoder)

Convenient Field types

from datetime import datetime, timedelta

from pydantic import (
    EmailStr,
    FutureDate,
    HttpUrl,
    PastDate,
    PositiveFloat,
    PositiveInt,
    confloat,
    conint,
    SecretStr,
)
from pydantic.dataclasses import dataclass


@dataclass
class User:
    user_id: int
    auth_date: PastDate
    expire_date: FutureDate
    email_addres: EmailStr
    user_page: HttpUrl
    age: PositiveInt
    height: PositiveFloat
    money: conint(gt=1000, lt=1024)
    score: confloat(ge=3.5, lt=5)
    password: SecretStr


user = User(
    user_id=1,
    auth_date=datetime.now() - timedelta(days=1),
    expire_date=datetime.now() + timedelta(days=1),
    email_addres="[email protected]",
    user_page="https://zenn.dev/",
    age=12,
    height=1.72,
    money=1001,
    score=3.5,
    password="hogehoge",
)

ref: https://pydantic-docs.helpmanual.io/usage/types/#pydantic-types

Validate field

from pydantic import validator
from pydantic.dataclasses import dataclass


@dataclass
class User:
    name: str
    nick_name: str
    password1: str
    password2: str

    @validator("name")
    def name_must_contain_space(cls, v):
        if " " not in v:
            raise ValueError("must contain a space")
        return v.title()

    @validator("password2")
    def passwords_match(cls, v, values, **kwargs):
        # vとvaluesにはそれぞれフィールド値と該当のフィールド以外のデータが入ったdictがある
        print("v", v)  # hogehoge
        print("values", values)  # {'name': 'くまもと はまち', 'nick_name': 'kagoshima', 'password1': 'hogehoge'}
        if "password1" in values and v != values["password1"]:
            raise ValueError("passwords do not match")
        return v

    @validator("nick_name")
    def username_alphanumeric(cls, v):
        assert v.isalnum(), "must be alphanumeric"
        return v


user = User(
    name="くまもと はまち",
    nick_name="kagoshima",
    password1="hogehoge",
    password2="hogehoge"
)

able to reuse validator function like this:

from pydantic import validator
from pydantic.dataclasses import dataclass


def name_must_contain_space(cls, v):
    if " " not in v:
        raise ValueError("must contain a space")
    return v.title()


@dataclass
class Person1:
    name: str

    # validators
    _normalize_name = validator("name", allow_reuse=True)(name_must_contain_space)


@dataclass
class Person2:
    name: str

    # validators
    _normalize_name = validator("name", allow_reuse=True)(name_must_contain_space)


kagoshima = Person1(name="kagoshima gyoko")
print("kagoshima.name", kagoshima.name)  # debug
kumamoto = Person2(name="kumamoto hamachi")
print("kumamoto.name", kumamoto.name)  # debug

load json file to pydantic object

def get_config() -> Config:
    with open(CONFIG_FILE_NAME, "rb") as f:
        d = json.load(f)
        return Config(**d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment