TT Lab
はじめる
学ぶ 学習パス コース

FastAPI — 型がそのまま契約だ

設定の誤字を既定値として扱わない

TT Labで続きを見る

目標

厳密な設定のパース・秘密情報の除去・アプリごとのスナップショットをテストします。

なぜ重要なのか

文字列のfalseをboolに変換したら、Trueになりました。本番でデバッグのレスポンスが有効になり、ステータスページには、設定の辞書全体が出力されました。環境変数は文字列なので、型の宣言だけでは、安全な値になりません。設定を読む時点と、公開する範囲も、アプリケーションの契約です。

ステップ

  1. /root/work/fa-config-lab/service.pyで、parse_bool(value)は、大文字と小文字を区別しないtrueまたはfalseだけを、boolとして返します。空白が付いているか、文字列でなければ、ValueErrorです。

最初に一度だけ準備してください。既存のファイルは上書きしません。

mkdir -p /root/work/fa-config-lab
test -e /root/work/fa-config-lab/service.py || cp /opt/fixtures/ten_labs/fa-config-lab/service.py /root/work/fa-config-lab/service.py
cd /root/work/fa-config-lab
  1. /root/work/fa-config-lab/service.pyで、parse_port(value)は、ASCIIの数字だけがある文字列をintに変換して、1–65535なら返します。それ以外は、ValueErrorです。

  2. /root/work/fa-config-lab/service.pyで、parse_timeout(value)は、文字列をfloatに変換して、0より大きく30以下の有限の値だけを返します。それ以外は、ValueErrorです。

  3. /root/work/fa-config-lab/service.pyで、required_token(env)は、TOKENが文字列で、stripしたあとに空でないときに、stripした値を返します。ないか、空の値は、ValueErrorです。

  4. /root/work/fa-config-lab/service.pyで、load_settings(env)は、service=envのSERVICE、または欠落時は'api'、debug=parse_bool(DEBUGの欠落時は'false')、port=parse_port(PORTの欠落時は'8000')、timeout=parse_timeout(TIMEOUTの欠落時は'5')、token=required_tokenの辞書です。空のSERVICEは、ValueErrorです。

  5. /root/work/fa-config-lab/service.pyで、public_settings(settings)は、serviceとdebugだけを持つ新しい辞書です。元のデータは変更しません。

  6. /root/work/fa-config-lab/service.pyで、snapshot(env)は、load_settingsの結果を返します。呼び出したあとにenvを変更しても、返された設定は変わりません。

  7. /root/work/fa-config-lab/service.pyで、create_app(env)は、snapshotをすぐに読み、不正な設定ならValueErrorでアプリの作成を失敗させます。GET /infoは、public_settingsだけを返します。別のenvで作ったアプリ同士で、設定を共有しません。

参考

真偽値を明示的にパースする

/root/work/fa-config-lab/service.pyで、parse_bool(value)は、大文字と小文字を区別しないtrueまたはfalseだけを、boolとして返します。空白が付いているか、文字列でなければ、ValueErrorです。

最初に一度だけ準備してください。既存のファイルは上書きしません。

mkdir -p /root/work/fa-config-lab
test -e /root/work/fa-config-lab/service.py || cp /opt/fixtures/ten_labs/fa-config-lab/service.py /root/work/fa-config-lab/service.py
cd /root/work/fa-config-lab

bool('false')はTrueです。許可する2つの文字列を、直接比較してください。

保存したら、bash /opt/lab/checks/fa-config-lab/01-contract.shで確認してください。

ポートの範囲を検査する

/root/work/fa-config-lab/service.pyで、parse_port(value)は、ASCIIの数字だけがある文字列をintに変換して、1–65535なら返します。それ以外は、ValueErrorです。

整数への変換が成功しても、有効なポートの範囲であるという意味ではありません。

保存したら、bash /opt/lab/checks/fa-config-lab/02-contract.shで確認してください。

タイムアウトを有限の値にする

/root/work/fa-config-lab/service.pyで、parse_timeout(value)は、文字列をfloatに変換して、0より大きく30以下の有限の値だけを返します。それ以外は、ValueErrorです。

NaNは、通常の比較で予想と違う動作をするので、isfiniteを確認します。

保存したら、bash /opt/lab/checks/fa-config-lab/03-contract.shで確認してください。

必須の秘密情報の欠落を拒否する

/root/work/fa-config-lab/service.pyで、required_token(env)は、TOKENが文字列で、stripしたあとに空でないときに、stripした値を返します。ないか、空の値は、ValueErrorです。

欠落した必須の秘密情報を、例の既定値で置き換えません。

保存したら、bash /opt/lab/checks/fa-config-lab/04-contract.shで確認してください。

欠落に対してだけ既定値を適用する

/root/work/fa-config-lab/service.pyで、load_settings(env)は、service=envのSERVICE、または欠落時は'api'、debug=parse_bool(DEBUGの欠落時は'false')、port=parse_port(PORTの欠落時は'8000')、timeout=parse_timeout(TIMEOUTの欠落時は'5')、token=required_tokenの辞書です。空のSERVICEは、ValueErrorです。

getの既定値と、orで既定値に切り替える書き方は、空文字列の扱いが違います。

保存したら、bash /opt/lab/checks/fa-config-lab/05-contract.shで確認してください。

公開する設定から秘密情報を取り除く

/root/work/fa-config-lab/service.pyで、public_settings(settings)は、serviceとdebugだけを持つ新しい辞書です。元のデータは変更しません。

トークンの値の一部をマスキングするよりも、フィールドそのものを公開しない契約を使います。

保存したら、bash /opt/lab/checks/fa-config-lab/06-contract.shで確認してください。

外部の変更と設定を分離する

/root/work/fa-config-lab/service.pyで、snapshot(env)は、load_settingsの結果を返します。呼び出したあとにenvを変更しても、返された設定は変わりません。

アプリの起動時点の設定を、あとで変わる入力の辞書から分離します。

保存したら、bash /opt/lab/checks/fa-config-lab/07-contract.shで確認してください。

起動の失敗と公開レスポンスを確認する

/root/work/fa-config-lab/service.pyで、create_app(env)は、snapshotをすぐに読み、不正な設定ならValueErrorでアプリの作成を失敗させます。GET /infoは、public_settingsだけを返します。別のenvで作ったアプリ同士で、設定を共有しません。

サーバーが起動したあと、最初のリクエストで設定エラーが表に出ないように、作成時に検証します。

保存したら、bash /opt/lab/checks/fa-config-lab/08-contract.shで確認してください。