FastAPI settings 관리
대부분의 웹 개발은 웹앱에 필요한 구성(Configuration)정보를 필요로 합니다. 그리고 그 구성 중에서 secret keys, database credentials 등은 각각의 환경마다 정보가 다를 수 있습니다. 장고 프레임워크에서는 이를 위한 모범사례로 환경마다 파일을 구분하고 Git에서 관리하는 것이었습니다. 다만, 이럴 경우 민감한(Sensitive) 정보들에 대해서는 환경 변수로 별도 관리되어야 합니다.
FastAPI에서는 내부적으로 pydantic 패키지를 사용하는데 이 패키지가 제공하는 모듈로 환경 변수를 읽어 settings를 관리합니다. 자 그럼 pydantic를 활용하여 간단한 예제를 보겠습니다.
from fastapi import FastAPI
from pydantic import BaseSettings
class Settings(BaseSettings):
APP_ENV: str = 'dev'
settings = Settings()
app = FastAPI()
@app.get("/")
async def root():
return {"app_env": settings.APP_ENV
- pydantic.BaseSettings를 import 하여 서브 클래스를 만듭니다.
- 서브 클래스에서 type hints, default 값으로 클래스 속성(class attribute)을 선언합니다.
- 서브 클래스인 Settings()를 인스턴스화 합니다.
- settings로 클래스 속성을 호출하여 리소스 내부에서 사용하면 됩니다.
다음은 환경 변수를 앱에 전달하여 APP_ENV 값을 치환해보도록 하겠습니다.
아래는 리눅스의 export 명령어로 환경 변수를 정의하여 치환합니다.
$ export APP_ENV=test
$ uvicorn main:app --reload
다른 방법으로 run server 시 환경 변수를 전달하여 치환합니다.
$ APP_ENV=test uvicorn main:app --reload
위 두 방법 모두 APP_ENV가 test로 치환됩니다.
Dotenv (.env) 지원.
Dotenv은 환경변수를 파일로 관리하여 쉽게 사용할 수 있게 하는 일반적인 패턴입니다. 이를 활용하면 환경별로 Config를 관리할 수 있습니다. pydantic에서는 외부 라이브러리를 사용하여 이를 지원합니다. 외부 라이브러리는 아래 커맨드로 설치해줍니다.
$ pip install pydantic[dotenv]
.env 파일을 생성하여 아래처럼 환경변수를 정의합니다.
APP_ENV=prod
REDIS_HOST=localhost
...
다음으로 Settings 클래스를 수정합니다.
from fastapi import FastAPI
from pydantic import BaseSettings
class Settings(BaseSettings):
APP_ENV: str = 'dev'
class Config:
env_file = '.env'
settings = Settings()
app = FastAPI()
@app.get("/")
async def root():
return {"app_env": settings.APP_ENV}
Settings의 inner class로 Config를 선언하고 .env 파일이 위치한 값으로 env_file을 정의합니다.
환경별로 .env 파일 분리하기.
제품을 서비스함에 있어서 통상적으로 다수의 환경을 필요로 합니다. 예를 들면, production, staging, development, test 환경들입니다. Dotenv를 활용하면 환경별로 Config를 쉽게 관리할 수 있습니다. 아래는 이를 위한 두 가지 방법입니다. 두 가지 방법 모두 환경별로 .env(prod.env, staging.env, test.env) 파일을 생성하는 것은 동일합니다. 시작하기 전에 ENV_STATE 환경변수를 우선 정의합니다.
$ export ENV_STATE=prod
1. 단일 클래스에서 _env_file kwargs를 사용하여 환경파일 정의.
import os
from pydantic import BaseSettings
class Settings(BaseSettings):
APP_ENV: str = 'dev'
class Config:
env_file = '.env'
settings = Settings(_env_file=f'{os.getenv("ENV_STATE")}.env')
2. 팩토리 메소드 패턴 사용.
from fastapi import FastAPI
from pydantic import BaseSettings
class GlobalSettings(BaseSettings):
ENV_STATE: str = 'dev'
APP_ENV: str = 'dev'
class Config:
env_file = '.env'
class DevSettings(GlobalSettings):
class Config:
env_file = 'dev.env'
class ProdSettings(GlobalSettings):
class Config:
env_file = 'prod.env'
class FactorySettings:
@staticmethod
def load():
env_state = GlobalSettings().ENV_STATE
if env_state == 'dev':
return DevSettings()
elif env_state == 'prod':
return ProdSettings()
settings = FactorySettings.load()
※ Dotenv 파일과 환경변수에 동일한 변수가 선언되었다면 Dotenv에서 load된 값보다 환경변수가 항상 우선시 됩니다.