Pydantic.BaseSetting을 사용한 환경변수 관리
.env 파일은 중요한 설정과 변수들을 정의한다. DB 정보, URL, API Key 등과 같은 민감한 정보를 코드에 하드코딩하지 않고 관리할 수 있어 필수적인 파일이다.그러나 개발⸰테스트⸰배포 환경이나
kojub.tistory.com
한 5개월 전에 Pydantic BaseSetting으로 환경변수 관리하는 config파일을 구성한 적 있다.
import os
from dotenv import load_dotenv
from pydantic_settings import BaseSettings
load_dotenv()
def _getenv(name: str, default: str = None) -> str | None:
value = os.getenv(name, default)
return value
class Configs(BaseSettings):
DB_ENGINE: str = _getenv("DB_ENGINE")
DB_USER: str = _getenv("DB_USER")
DB_PASSWORD: str = _getenv("DB_PASSWORD")
DB_HOST: str = _getenv("DB_HOST")
DB_PORT: str = _getenv("DB_PORT")
DATA_BASE: str = _getenv("DATA_BASE")
DATABASE_URI: str = (
"{db_engine}://{user}:{password}@{host}:{port}/{database}".format(
db_engine=DB_ENGINE,
user=DB_USER,
password=DB_PASSWORD,
host=DB_HOST,
port=DB_PORT,
database=DATA_BASE,
)
)
configs = Configs()
더 나은 방식에 대한 고민의 계기
이번에 한국관광공사 공모전에 참가하게 되면서, 새로운 프로젝트를 생성하게 되었다. ( 또 새로운 프로젝트 생성했다 젠장...마무리 짓는 것이 목표 ^^;;; )
시간 안에 완성도 있는 서비스를 만들어야 하기 때문에 백엔드 스택은 제일 익숙한..원래 사용하던...FastAPI + Postgresql + Sqlalchemy 조합을 그대로 가져가게 되었다.
하지만 매번 같은 방식으로 초기 세팅을 하다 보면 어느 순간 발전이 없다는 생각이 들어서, 이번에는 환경 세팅과 config 구성부터 조금씩 변화를 줘보기로 했다.
더 나은 방식 포인트
1. class Config로 .env파일을 자동처리할 수 있다.
load_dotenv()
def _getenv(name: str, default: str = None) -> str | None:
value = os.getenv(name, default)
return value
이 코드는 로컬에 있는 .env파일을 로드하기 위해 필요한 코드다.
따로 커스텀함수 까지 만들어서 .env파일에 정의된 환경변수를 가져오게끔 처리를 한거다.
class Configs(BaseSettings):
...
class Config:
env_file = ".env"
위처럼 Pydantic에서 Class Config를 사용하면, 자동으로 .env파일을 로드할 수 있다.
이게 좋은 게 뭐냐면,
- 테스트용 DB로 connection 걸어야 할 땐, env_file값만 바꿔주면 된다는 점이다.
- 또, 불필요하게 load_dotenv() 메소드를 명시적으로 작성할 필요도 없고 __getenv메소드 처럼 커스텀 메소드를 만들 필요도 없다.
2. 실행 지점에서만 실패하게 만드는 설계 패턴 ( @property )
DATABASE_URI: str = (
"{db_engine}://{user}:{password}@{host}:{port}/{database}".format(
db_engine=DB_ENGINE,
user=DB_USER,
password=DB_PASSWORD,
host=DB_HOST,
port=DB_PORT,
database=DATA_BASE,
)
)
이렇게 되어 있다보니까, class 정의 시점에서 DB_ENGINE같은 변수를 전부 가져오고 .format도 즉시실행되어 DATABASE_URL에 할당된다. 그래서 .env파일이나 시스템 환경변수 주우 하나라도 없으면 서버 실행하자마자 에러가 난다.
class Configs(BaseSettings):
DB_ENGINE: str = _getenv("DB_ENGINE")
...
@property
def DATABASE_URL(self):
return f"{self.DB_ENGINE}://..."
이렇게 바꿔주면, DATABASE_URL을 호출하기 전까진 실행되지 않는다.
그래서 configs.DATABASE_URL을 실제로 사용할 시점에 에러가 발생한다. 즉, Config() 객체 생성엔 문제가 없다.
이게 왜 좋냐면,
- 유닛테스트나 로컬 서버를 띄울 때 꼭 DB 연결이 필요한 게 아니면, 설정이 완전하지 않아도 앱이 뜰 수 있다.
- 꼭 필요한 시점에만 오류를 만날 수 있어서 디버깅도 더 쉬워진다.
그래서 완성된 Config 파일은요
from pydantic_settings import BaseSettings
class Configs(BaseSettings):
DB_ENGINE: str
DB_USER: str
DB_PW: str
DB_HOST: str
DB_PORT: str
DATA_BASE: str
@property
def DATABASE_URL(self):
return f"{self.DB_ENGINE}://{self.DB_USER}:{self.DB_PW}@{self.DB_HOST}:{self.DB_PORT}/{self.DATA_BASE}"
class Config:
env_file = ".env"
'Python' 카테고리의 다른 글
black으로 코드스타일 포매팅 (0) | 2025.02.11 |
---|---|
언더스코어 인 파이썬 ( 파이썬 객체지향적으로 사용하기 ) (1) | 2025.01.30 |
Pydantic.BaseSetting을 사용한 환경변수 관리 (0) | 2025.01.26 |
mypy에서 가상 환경 파일 무시하기: venv 디렉토리 제외 설정 방법 (0) | 2024.08.21 |
딕셔너리 활용하기 (1) | 2023.06.03 |