Python 데이터클래스 + 검증
Python dataclass로 만드는 도메인 모델. __post_init__ 검증, to_dict/from_dict 직렬화 포함.
from __future__ import annotations
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Optional, List
import re
@dataclass
class Address:
street: str
city: str
country: str
postal_code: str
def __post_init__(self) -> None:
self.postal_code = self.postal_code.strip()
if not self.postal_code:
raise ValueError("postal_code cannot be empty")
self.country = self.country.upper()
@dataclass
class User:
name: str
email: str
age: int
address: Address
roles: List[str] = field(default_factory=list)
bio: Optional[str] = None
created_at: datetime = field(default_factory=datetime.utcnow)
is_active: bool = True
_EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
def __post_init__(self) -> None:
self.email = self.email.lower().strip()
if not self._EMAIL_RE.match(self.email):
raise ValueError(f"Invalid email: {self.email!r}")
if not (0 <= self.age <= 150):
raise ValueError(f"Age must be 0–150, got {self.age}")
if not self.name.strip():
raise ValueError("name cannot be blank")
def to_dict(self) -> dict:
d = asdict(self)
d["created_at"] = self.created_at.isoformat()
return d
@classmethod
def from_dict(cls, data: dict) -> User:
data = data.copy()
data["address"] = Address(**data["address"])
if "created_at" in data:
data["created_at"] = datetime.fromisoformat(data["created_at"])
return cls(**data)
def add_role(self, role: str) -> None:
role = role.lower().strip()
if role and role not in self.roles:
self.roles.append(role)
def __repr__(self) -> str:
return f"User(name={self.name!r}, email={self.email!r}, age={self.age})"