본문 바로가기
Tip/Python

[Python] 환경변수 사용하는 방법 비교

by JavaPark 2025. 11. 6.

방안 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 사용)를 추천합니다.

이유:

  1. 환경 변수 이름이 코드에 명시적으로 보임 (validation_alias)
  2. 타입 안전성 보장 (자동 검증)
  3. description으로 문서화 강화
  4. FastAPI 자동 문서(/docs)에서 설정 확인 가능