| name | pydantic-v2 |
| description | Pydantic v2 and pydantic-settings — BaseModel, field validation, config structs, and BaseSettings for CLI/config management (replaces argparse.Namespace) |
Pydantic v2
BaseModel basics
from __future__ import annotations
from pydantic import BaseModel, Field, field_validator, model_validator
class TrainConfig(BaseModel):
learning_rate: float = Field(default=1e-3, gt=0, le=1.0)
batch_size: int = Field(default=256, gt=0)
n_epochs: int = Field(default=100, ge=1)
hidden_dim: int = 128
dropout: float = Field(default=0.1, ge=0.0, lt=1.0)
model_name: str = "stgraph"
device: str = "cuda"
cfg = TrainConfig(learning_rate=0.01, batch_size=512)
cfg.model_dump()
cfg.model_dump_json()
TrainConfig.model_validate({"learning_rate": 0.01})
Field validators (v2 syntax)
from pydantic import field_validator, model_validator
from typing import Self
class Config(BaseModel):
tau: float = 0.5
n_clusters: int = 10
dataset: str = "elliptic"
@field_validator("tau")
@classmethod
def tau_in_range(cls, v: float) -> float:
if not 0.0 < v < 1.0:
raise ValueError(f"tau must be in (0, 1), got {v}")
return v
@field_validator("dataset")
@classmethod
def valid_dataset(cls, v: str) -> str:
allowed = {"elliptic", "eth-phishing", "b4e"}
if v not in allowed:
raise ValueError(f"dataset must be one of {allowed}")
return v
@model_validator(mode="after")
def check_consistency(self) -> Self:
if self.n_clusters > 50 and self.tau < 0.3:
raise ValueError("low tau with many clusters is unstable")
return self
Nested models
class OptimizerConfig(BaseModel):
name: str = "adam"
lr: float = 1e-3
weight_decay: float = 1e-5
class ExperimentConfig(BaseModel):
train: TrainConfig = TrainConfig()
optimizer: OptimizerConfig = OptimizerConfig()
seed: int = 42
output_dir: str = "outputs/"
import yaml
with open("config.yaml") as f:
raw = yaml.safe_load(f)
cfg = ExperimentConfig.model_validate(raw)
pydantic-settings (replaces argparse)
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_prefix="STGRAPH_",
env_file=".env",
env_file_encoding="utf-8",
cli_parse_args=True,
)
lr: float = 1e-3
batch_size: int = 256
dataset: str = "elliptic"
device: str = "cuda"
output_dir: str = "outputs/"
settings = Settings()
Serialization / deserialization
cfg.model_dump(exclude_unset=True)
import json
with open("config.json", "w") as f:
f.write(cfg.model_dump_json(indent=2))
with open("config.json") as f:
cfg = TrainConfig.model_validate_json(f.read())
new_cfg = cfg.model_copy(update={"lr": 0.01})
v1 → v2 migration gotchas
| v1 | v2 |
|---|
@validator | @field_validator (classmethod, different signature) |
class Config: | model_config = ConfigDict(...) |
.dict() | .model_dump() |
.json() | .model_dump_json() |
.parse_obj() | .model_validate() |
@root_validator | @model_validator(mode="after") |
Field(env=...) | use pydantic-settings BaseSettings |
Pitfalls
@field_validator must be a @classmethod in v2 — forgetting this causes a silent no-op.
- Default mutable values (lists, dicts) must use
Field(default_factory=list) — same as v1.
model_config replaces class Config — mixing both causes the inner Config class to be treated as a model field.
BaseSettings with cli_parse_args=True requires pydantic-settings >= 2.3.
- Use
model_validate not parse_obj — the latter is removed in v2.