Python 앱에서 Pydantic으로 settings.toml 읽기와 검증하기
이 모듈은 settings.toml 파일을 읽고, tomllib로 파싱하며, lru_cache로 결과를 캐싱하고, Pydantic으로 섹션을 검증합니다. 설정 로직을 앱 파라미터와 분리해 중복 디스크 읽기를 줄이고 타입 안전성을 보장합니다.
from functools import lru_cache
from pathlib import Path
from tomllib import load
from typing import Any, Type, TypeVar
from pydantic import BaseModel, SecretStr
ConfigType = TypeVar("ConfigType", bound=BaseModel)
class BotConfig(BaseModel):
token: SecretStr
@lru_cache
def parse_config_file() -> dict[str, Any]:
config_path = Path(__file__).resolve().parent.parent.joinpath("settings.toml")
if not config_path.exists():
error = "설정 파일을 찾을 수 없습니다"
raise ValueError(error)
with open(config_path, "rb") as file:
config_data = load(file)
return config_data
def get_config(model: Type[ConfigType], root_key: str) -> ConfigType:
config_dict = parse_config_file()
if root_key not in config_dict:
error = f"키 {root_key}를 찾을 수 없습니다"
raise ValueError(error)
return model.model_validate(config_dict[root_key])
왜 TOML과 Pydantic을 설정에 사용할까
TOML 파일은 사람이 읽기 쉽고 Python 딕셔너리로 깔끔하게 파싱됩니다. [bot] 같은 섹션은 중첩 딕셔너리로 변환됩니다. Pydantic은 구조를 검증하고 타입 변환을 처리하며, SecretStr로 비밀 값을 마스킹합니다.
TOML 예시:
[bot]
token = "123456:ABCDEF"
파싱 결과는 {"bot": {"token": "123456:ABCDEF"}}가 됩니다. 검증으로 BotConfig 객체가 생성됩니다.
@lru_cache로 캐싱하면 반복 읽기를 건너뜁니다: 첫 호출에서 파싱하고 이후는 캐시를 사용합니다. parse_config_file.cache_clear()로 재로드할 수 있습니다.
pathlib로 경로 처리하기
Path(__file__).resolve().parent.parent.joinpath("settings.toml")은 프로젝트 상위 디렉터리의 파일에 대한 절대 경로를 생성합니다. exists()로 존재 여부를 확인하고 parent 속성으로 검증을 간소화합니다.
Path.joinpath(): 플랫폼 간 경로 세그먼트를 추가합니다.resolve(): 심볼릭 링크를 절대 경로로 해결합니다."rb"로 열기:tomllib.load()가 바이너리 읽기를 요구합니다.
TypeVar와 Pydantic으로 타입 힌트
ConfigType = TypeVar("ConfigType", bound=BaseModel)으로 get_config이 어떤 Pydantic 모델과도 작동하며 전달된 타입의 인스턴스를 반환합니다.
Type[ConfigType]: 모델 클래스를 기대합니다. 인스턴스가 아닙니다.model_validate(): 딕셔너리를 검증된 객체로 변환하며, 실패 시ValidationError를 발생시킵니다.- 딕셔너리에
Any: TOML은 str, int, bool, dict 등 타입이 섞여 있습니다.
다른 섹션으로 확장:
class DatabaseConfig(BaseModel):
host: str
port: int
user: str
config = get_config(DatabaseConfig, "database")
캐싱과 데코레이터
@lru_cache는 인수로 결과를 저장합니다(여기서는 인수 없어 단순). LRU는 한도 초과 시 오래된 항목을 제거하지만, 설정에는 하나의 슬롯으로 충분합니다.
장점:
- I/O 감소: 디스크 읽기 한 번.
- 멱등성: 여러
get_config호출이 하나의 파싱을 공유. - 스레드 안전: 표준 라이브러리 데코레이터가 처리.
단점: TOML 파일 변경 시 cache_clear()까지 무시됩니다.
모듈 구조
- 모델(
BotConfig): 로더와 독립적. parse_config_file(): 전체 TOML 파싱.get_config(): 섹션 추출 및 검증.
이 관심사 분리로 테스트 용이성 향상: 경로나 TOML 문자열 모킹 가능.
주요 팁:
tomllib(Python 3.11+) 사용, 이전 버전은tomli대체.- 사용 전 Pydantic으로 설정 검증.
- 파싱 캐싱하되 재로드 지원 추가.
- 로그/디버그에서 마스킹 위해
SecretStr에 비밀 저장. - 플랫폼 호환 위해
pathlib로 경로 구성.
— Editorial Team
아직 댓글이 없습니다.