django-environ 是用來管理設定的套件,等等,一般設定不就是寫在 django settings 裡面嗎?為什麼還需要套件來管理呢?你想想,把設定寫死在程式裡,第一個未來要更動裡面的設定值時,得再改動程式,再者,考慮一下要佈署到不同的環境時,要怎麼更動設定呢?
django-environs 就是為此而生,透過這個套件,你可以把設定抽離到程式之外,程式裡只要聲明你要讀取哪些設定即可,而設定則可以是環境變數、設定檔等等的,這樣未來設定更動的時候,只要調整外部的這些設定來源就行,不會再次改動到程式,這樣就方便很多啦。
在我們設計一個服務時,通常會考慮到所謂的 12-factor,什麼是 12-factor 呢?12-factor 是講以下的這十二件事情:
- Codebase 程式碼:一套程式碼對應到一個應用程式,如果有多個應用程式共用一部分程式碼,那麼就該把這部份包裝函式庫。
- Dependencies 相依性:要把相依性納入管理,利用 pyproject.toml (poetry), requirements.txt (pip) 等來做記錄,未來要接手的工程師就可以透過這些檔案來還原當時的開發/生產環境。
- Config 配置變數:不要把設定直接放在程式碼裡面,而是把設定放在環境變數、設定檔或是 k8s/docker 的 secrets 裡,同時設定也要納入管理,並加上版本號碼。
- Backing Services 後端支援服務:所有的後端支援服務都應該視為外接的資源,舉凡資料庫、快取、SMTP 等等都是。這樣未來在切換服務時,只需要改動設定就可以切換服務來源。未來在擴充上,也能得到很大的助益。
- Build, Release and Run 建構、發行與執行:確實分離建構與執行的階段。
- 構建是指把程式碼進行編譯後打包的過程,有些程式語言沒有編譯,那麼這個過程就會是單純的打包。佈署,則是將建置後的產出物跟設定相結合,放到指定的環境去。執行就是依照指定的版本,在環境中啟動、執行。
- 需要有版本號,方便追蹤,有問題時,可以快速回復到指定版本。
- 盡量把這過程自動化,避免人為過失。
- Process 處理程序:要以「無狀態」的方式來執行,也就是說,應用程式本身不保存長期狀態或儲存資料,而是把長期狀態或資料儲存到外部服務去,這樣的好處是,應用程式不需要考慮資料遺失的問題,在應用程式意外關閉時,可以隨時重新啟動。
- Port binding 連接埠:透過連接埠對外提供服務,避免對外暴露過多的介面。
- Concurrency 同步執行:借助外部的程序管理系統來管理程序,也因為程序的特性,可以容易水平擴展。
- Disposability 快速啟用與終止:因為是程序,可以快速的啟用跟終止,啟動時間也可以達到最少。另外也要優雅的處理 SIGTERM ,讓程序可以不用等待過多當前的處理才退出。
- 環境的一致性:保持開發環境與生產環境的一致,除錯會比較容易。另外也要盡可能的頻繁佈署,讓所有人都對這個過程與環境有一定的認識。
- Logs 記錄:程序統一使用 stdout 輸出 log,那 log 可以再透過其他的服務,例如 flutentd / telegraf 等來輸出或擷取。
- Admin processes:要跟正常程序使用同樣的環境,也需要遵守上述的準則。
簡單的說,這套件主要是遵循 12 factor app 的 Config 配置變數 準則。
安裝
poetry add django-environ
使用
在 settings.py 裡,加入
import environ
ROOT_DIR = (
environ.Path(__file__) - 1
)
env = environ.Env()
# 依據環境變數來決定是否讀取 .env
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
if READ_DOT_ENV_FILE:
# OS environment variables take precedence over variables from .env
env.read_env(str(ROOT_DIR.path(".env")))
DEBUG = env.bool("DJANGO_DEBUG", False)
SECRET_KEY = env('DJANGO_SECRET_KEY')
DATABASES = {"default": env.db("DATABASE_URL")}
EMAIL_CONFIG = env.email_url(
'EMAIL_URL', default='smtp://user:password@localhost:25')
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["localhost","example.com"])
解釋一下上面的程式
- environ.Path(file) 是取得 settings 檔案的目錄,environ 覆寫了 Path 的 - 運算,可以便捷的取得 settings 檔案的相對目錄,-1 表示上一層,-2表示上兩層,以此類推。
- env.read_env() 是讀取指定的檔案,這邊是表示讀取 .env 這個檔案。那這邊預先判斷有沒有 DJANGO_READ_DOT_ENV_FILE 這個環境變數,有的話而且內容為 True 才去讀取。
- env.bool() 表示讀取 DJANGO_DEBUG ,而 DJANGO_DEBUG 是一個布林型態的設定,如果沒有設定,預設值為 False。
- env() 表示讀取 DJANGO_SECRET_KEY 這個字串型態的設定,這邊沒有填預設值,所以在沒有設定的時候,就會丟出 ImproperlyConfigured 這個例外,這樣就能很明確的讓人知道必須要填這個設定。
- env.db() 是表示讀取資料庫連接字串設定,這邊這個 db 主要是幫你多做解析資料庫連接字串,並轉換為 Django 可以接受的 dict 型態。
- env.email_url() 也跟 db 一樣的道理。
- env.list() 是表示設定內容是一個 List 型態的設定,實際上是以 "," 分隔的字串,env.list() 會幫你做 split(",") ,轉換為 List 型態。
好啦,現在我們可以執行看看 runserver
poetry run python manage.py runserver
這時就會看到像這樣的錯誤訊息,提醒你要加這個設定啦
django.core.exceptions.ImproperlyConfigured: Set the DATABASE_URL environment variable
現在我們補上 DJANGO_SECRET_KEY 以及 DATABASE_URL 的設定:
export DJANGO_SECRET_KEY=this_is_a_test_secret_key
export DATABASE_URL=sqlite://$(pwd)/db.sqlite3
然後再執行,就可以看到順利啟動啦。
django-environ 更多的用法可以參考 django-environ 網站上的說明文件:https://django-environ.readthedocs.io/en/latest/
參考資料