방안 1: os.getenv() 명시적 사용 (가장 직관적)
import os
from typing import Any
def get_env(key: str, default: Any = None) -> Any:
"""환경 변수 가져오기 (명시적)"""
return os.getenv(key, default)
class Settings:
"""Application settings
모든 설정은 환경 변수에서 로드됩니다.
환경 변수가 없으면 기본값이 사용됩니다.
"""
# Application
app_name: str = get_env("APP_NAME", "AI Assistant Portal")
app_env: str = get_env("APP_ENV", "development")
debug: bool = get_env("DEBUG", "true").lower() == "true"
# Database (asyncpg 필수)
database_url: str = get_env(
"DATABASE_URL",
"postgresql+asyncpg://postgres:postgres@localhost:5432/ai_portal"
)
# API
api_v1_prefix: str = get_env("API_V1_PREFIX", "/api/v1")
# Security (프로덕션에서는 환경 변수 필수)
secret_key: str = get_env(
"SECRET_KEY",
"your-secret-key-change-in-production-use-openssl-rand-hex-32"
)
algorithm: str = get_env("ALGORITHM", "HS256")
access_token_expire_minutes: int = int(get_env("ACCESS_TOKEN_EXPIRE_MINUTES", "1440"))
# CORS
cors_origins: list[str] = get_env(
"CORS_ORIGINS",
"http://localhost:3000,http://localhost:5173"
).split(",")
# Demo credentials (개발 환경 전용)
demo_email: str = get_env("DEMO_EMAIL", "demo@example.com")
demo_password: str = get_env("DEMO_PASSWORD", "password123!")
settings = Settings()
장점:
- ✅ 환경 변수 이름이 코드에 명시적으로 보임
- ✅ 어디서 값이 오는지 한눈에 파악 가능
- ✅ 간단하고 직관적
단점:
- ❌ 타입 검증 수동 처리 필요 (int, bool 변환)
- ❌ Pydantic의 검증 기능 사용 불가
- ❌ .env 파일 자동 로드 안됨 (python-dotenv 필요)
방안 2: Pydantic Field로 환경 변수 명시 (타입 안전성 + 명시성)
from typing import Any
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables
환경 변수 우선순위:
1. 시스템 환경 변수 (K8s ConfigMap/Secret) - 최우선
2. .env 파일 (.env.local, .env.docker)
3. 기본값 (개발 환경 fallback)
"""
# Application
app_name: str = Field(
default="AI Assistant Portal",
description="애플리케이션 이름",
validation_alias="APP_NAME" # 환경 변수 이름 명시
)
app_env: str = Field(
default="development",
description="환경: development, staging, production",
validation_alias="APP_ENV"
)
debug: bool = Field(
default=True,
description="디버그 모드 (프로덕션에서는 false)",
validation_alias="DEBUG"
)
# Database (asyncpg 필수)
database_url: str = Field(
default="postgresql+asyncpg://postgres:postgres@localhost:5432/ai_portal",
description="PostgreSQL 연결 URL (asyncpg 드라이버)",
validation_alias="DATABASE_URL"
)
# API
api_v1_prefix: str = Field(
default="/api/v1",
description="API v1 경로 접두사",
validation_alias="API_V1_PREFIX"
)
# Security (프로덕션에서는 환경 변수 필수)
secret_key: str = Field(
default="your-secret-key-change-in-production-use-openssl-rand-hex-32",
description="JWT 서명 키 (프로덕션에서는 openssl rand -hex 32로 생성)",
validation_alias="SECRET_KEY"
)
algorithm: str = Field(
default="HS256",
description="JWT 알고리즘",
validation_alias="ALGORITHM"
)
access_token_expire_minutes: int = Field(
default=60 * 24,
description="액세스 토큰 만료 시간 (분)",
validation_alias="ACCESS_TOKEN_EXPIRE_MINUTES"
)
# CORS
cors_origins: list[str] | str = Field(
default=["http://localhost:3000", "http://localhost:5173"],
description="CORS 허용 오리진 (쉼표로 구분)",
validation_alias="CORS_ORIGINS"
)
# Demo credentials (개발 환경 전용)
demo_email: str = Field(
default="demo@example.com",
description="데모 계정 이메일 (개발 환경 전용)",
validation_alias="DEMO_EMAIL"
)
demo_password: str = Field(
default="password123!",
description="데모 계정 비밀번호 (개발 환경 전용)",
validation_alias="DEMO_PASSWORD"
)
@field_validator("cors_origins", mode="before")
@classmethod
def parse_cors_origins(cls, v: Any) -> list[str]:
if isinstance(v, str):
return [origin.strip() for origin in v.split(",")]
return v
@model_validator(mode="after")
def validate_production_settings(self) -> "Settings":
if self.app_env == "production":
if "your-secret-key" in self.secret_key.lower():
raise ValueError(
"프로덕션 환경에서는 SECRET_KEY 환경 변수를 반드시 설정해야 합니다. "
"openssl rand -hex 32 명령어로 안전한 키를 생성하세요."
)
if self.debug:
raise ValueError("프로덕션 환경에서는 DEBUG=false로 설정해야 합니다.")
return self
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
settings = Settings()
장점:
- ✅ 환경 변수 이름이 validation_alias로 명시됨
- ✅ Pydantic의 타입 검증 자동 적용
- ✅ description으로 설정 설명 추가 가능
- ✅ .env 파일 자동 로드
- ✅ 검증 로직 통합 가능
단점:
- ⚠️ 코드가 다소 길어짐
- ⚠️ Field() 사용법 익혀야 함
방안 3: 혼합 방식 (실용적 절충안)
import os
from typing import Any
from pydantic import field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
def env(key: str, default: Any = None) -> Any:
"""환경 변수 가져오기 헬퍼 함수"""
return os.getenv(key, default)
class Settings(BaseSettings):
"""Application settings loaded from environment variables
모든 설정은 다음 환경 변수에서 로드됩니다:
- APP_NAME, APP_ENV, DEBUG
- DATABASE_URL (필수: asyncpg)
- SECRET_KEY (프로덕션 필수)
- CORS_ORIGINS (쉼표로 구분)
환경 변수가 없으면 아래 기본값이 사용됩니다.
"""
# Application (ENV: APP_NAME, APP_ENV, DEBUG)
app_name: str = "AI Assistant Portal"
app_env: str = "development" # development, staging, production
debug: bool = True
# Database (ENV: DATABASE_URL - asyncpg 필수)
database_url: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/ai_portal"
# API (ENV: API_V1_PREFIX)
api_v1_prefix: str = "/api/v1"
# Security (ENV: SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES)
secret_key: str = "your-secret-key-change-in-production-use-openssl-rand-hex-32"
algorithm: str = "HS256"
access_token_expire_minutes: int = 60 * 24 # 24 hours
# CORS (ENV: CORS_ORIGINS - 쉼표로 구분)
cors_origins: list[str] | str = ["http://localhost:3000", "http://localhost:5173"]
# Demo credentials (ENV: DEMO_EMAIL, DEMO_PASSWORD - 개발 환경 전용)
demo_email: str = "demo@example.com"
demo_password: str = "password123!"
@field_validator("cors_origins", mode="before")
@classmethod
def parse_cors_origins(cls, v: Any) -> list[str]:
if isinstance(v, str):
return [origin.strip() for origin in v.split(",")]
return v
@model_validator(mode="after")
def validate_production_settings(self) -> "Settings":
if self.app_env == "production":
if "your-secret-key" in self.secret_key.lower():
raise ValueError(
"프로덕션 환경에서는 SECRET_KEY 환경 변수를 반드시 설정해야 합니다. "
"openssl rand -hex 32 명령어로 안전한 키를 생성하세요."
)
if self.debug:
raise ValueError("프로덕션 환경에서는 DEBUG=false로 설정해야 합니다.")
return self
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
settings = Settings()
장점:
- ✅ 주석으로 환경 변수 이름 명시 (간결)
- ✅ Pydantic의 타입 검증 유지
- ✅ 코드 간결성 유지
- ✅ docstring에서 전체 환경 변수 목록 확인 가능
추천 방안
저는 방안 2 (Pydantic Field 사용)를 추천합니다.
이유:
- 환경 변수 이름이 코드에 명시적으로 보임 (validation_alias)
- 타입 안전성 보장 (자동 검증)
- description으로 문서화 강화
- FastAPI 자동 문서(/docs)에서 설정 확인 가능
'Tip > Python' 카테고리의 다른 글
| 파이썬 코드 품질 마스터를 위한 종합 가이드: 원칙부터 프로덕션까지 (0) | 2025.10.23 |
|---|---|
| 파이썬 품질 관리의 모든 것: 핵심 지표와 테스트 전략으로 코드 품질 높이기 (0) | 2025.10.22 |
| pigar vs pipreqs (0) | 2025.02.24 |
| Ruff를 이용한 Python 코드 품질 관리 및 VS Code에서의 Python 코딩Ruff 소개 (0) | 2024.01.28 |