設定ファイルを考えてみよう
環境変数を利用する
設定情報を環境変数に設定しておくと柔軟なプログラムを書くことができます。
例えば次のようなときなどです...
パスワードやアクセストークンをハードコーディングしたくない
本番環境と開発環境をうまく切り替えたい
Linux系プラットフォームで、シェルの環境変数として MAIL_PASSWORD を設定したとします。
code: bash
export MAIL_PASSWORD='MY_SECRET_PASSWORD'
これを python から次のように設定された値を取得することができます。
code: sample.py
import os
mail_password = os.environ.get("MAIL_PASSWORD", None)
ただし、設定する環境変数の数が多くなってくると、シェル側でファイルにしておき、
source コマンドで読み込んでおく必要があります。
code: bash
source $HOME/my_env.sh
python sample.py
python-dotenv を使った環境変数の読み込み
python-dotenv がインストールされていれば、環境変数を設定を自動化することができます。
まず python-dotenv をインストールしておきましょう。
code: bash
$ pip install python-dotenv
code: python
import os
from dotenv import load_dotenv
load_dotenv(verbose=True)
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(dotenv_path)
python 側のコードはすこし増えますが、毎回シェル側のコマンドを実行する必要がなくなります。
注意しなければいけないことは、環境変数に設定されているのは文字列であるということです。数値として扱いたい場合は int() などを使って適宜変換する必要があります。
python-dotenv の利用例
あるサービスを提供するようなプログラムでは、開発環境と本番環境と区別したいけれど、
コードの修正は可能なかぎり避けたいというようなことがあります。
こうしたときに、python-dotenv が便利になります。
次のコードはLinuxの systemd で起動されるWebサービスの一例です。
code: bash
Description=regist_sshpubkey daemon
Requires=regist_sshpubkey.socket
After=network.target
PIDFile=/run/gunicorn/pid
Usder=webapp
Group=webapp
RuntimeDirectory=gunicorn
WorkingDirectory=/var/webapp/projects/regist_sshpubkey/
ExecStart=/var/webapp/projects/regist_sshpubkey/RUN_APP.sh
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true
WantedBy=multi-user.target
ここで呼び出される RUN_APP.sh は次のようになっています。
code: bash
MYHOST=$( hostname -s )
CONDADIR=/opt/anaconda3
BASEDIR=$( dirname $0 )
APP_HOME=/var/webapp
CONDAENV=${APP_HOME}/.conda/envs/webapp
. "${CONDADIR}/etc/profile.d/conda.sh"
else
export PATH="${CONDADIR}/bin:$PATH"
fi
conda activate ${CONDAENV} && {
case "${MYHOST}" in
useradm|user_adm)
cp ${BASEDIR}/dot.env.production ${BASEDIR}/.env
;;
*)
cp ${BASEDIR}/dot.env.debug ${BASEDIR}/.env
;;
esac
source ${BASEDIR}/.env
GUNICORN=$( which gunicorn)
exec ${GUNICORN} app:app -b ${APP_HOST}:${APP_PORT} \
--config ${BASEDIR}/scripts/gunicorn_config.py
}
実行されるサーバーのホスト名が本番環境のものであれば、本番環境用の設定ファイルdot.env.production を使い、それ以外では開発環境用のdot.env.debug を使うように
切り替えています。
アプリケーションを起動する前に明示的に source ${BASEDIR}/.env として環境変数をセットしていますが、これは、アプリケーション側では python-dotenv を利用して読み込めるのですが、
起動時に与えている ${APP_HOST}:${APP_PORT} で使うために行っています。
pydantic を使った環境変数の読み込み
シェルの環境変数から情報を読み込むと、設定情報を文字列として取得できます。
これでも良いのですが、整数や浮動小数点として扱いたいときはどこかで変換する必要がでてきます。
こうしたときは、pydantic を使うとデフォルト値をセットしたり、指定した型に変換してくれます。また、 python-dotenv がインストールされていれば、環境変数から読み取ってくれます。
まず pydantic をインストールしておきましょう。
code: bash
$ pip install pydantic
実際にコードを見てみましょう。
code: baseconfig.py
import os
import pydantic
basedir = os.path.abspath(os.path.dirname(__file__))
class BaseSettings(pydantic.BaseSettings):
class Config:
env_prefix = ""
env_file = "myapp.env"
env_file_encoding = "utf-8"
use_enum_values = True
env_file で 環境設定をするファイル名を指定できます。
code: config.py
from baseconfig import basedir, BaseSettings
from typing import Optional
class MAILSettings(BaseSettings):
MAIL_SERVER: str = "smtp.gmail.com"
MAIL_PORT: int = 587
MAIL_USE_TLS: bool = True
MAIL_USE_SSL: bool = False
MAIL_USERNAME: Optionalstr = None MAIL_PASSWORD: Optionalstr = None MAIL_DEFAULT_SENDER: Optionalstr = "iisaka51@gmail.com" # for debug
MAIL_DEBUG: bool = False
MAIL_SUPPRESS_SEND: bool = False
使うときは、次のようにします。
code: python
In 1: # %load pydantic_deom.py ...: from config import MAILSettings
...:
...: conf = MAILSettings()
...: print(conf.MAIL_PORT)
...: print(type(conf.MAIL_PORT))
587
<class 'int'>
クラス変数として登録すると、あとは pydantic が env_file で与えたファイルから読み取って、タイプヒントで定義した型に自動的に変換してくれます。
参照するときも、クラス変数にアクセスするだけなので簡単ですね。
python 標準モジュール typing にはいろいろな型のタイプヒントを提供しています。
前述の例では、Optionalを使って、設定されていないときは None にもなることを定義しています。
オブジェクトには決まった値しかとらないことを保証したいときは、typing.Literal を使うこともできますが、 pydantic-choices がインストールされていれば次のようにも記述することができ、コードが読みやすくなります。
code: python
from pydantic_choices import choice