タイプヒントを使用してDjango設定を合理化する:Pydanticチュートリアル

公開: 2022-07-22

新しい環境を追加するための堅牢でスケーラブルな方法がなかったため、Djangoプロジェクトは私を苛立たせていました。 pydanticとPythonタイプのヒントを組み合わせることで、必要な強力な基盤を構築しました。

PEP 484で説明されているように、タイプヒントは静的分析をサポートしますが、これらの同じ注釈は実行時にも使用できます。 pydanticのようなサードパーティパッケージは、この追加のメタデータを使用するランタイムタイプチェックを提供します。 Pydanticは、Pythonタイプのヒントを使用して、設定メタデータの管理とランタイムデータ検証の実行を支援します。

このpydanticチュートリアルでは、Djangoでpydantic設定管理を使用することの広範囲にわたるプラスの効果を示します。

私たちの構成は、Twelve-FactorAppWebサイトに記載されているベストプラクティスに準拠しています。

  1. 非定数および秘密の構成を環境変数として定義します。
  2. 開発環境では、 .envファイルで環境変数を定義し、 .env.gitignoreに追加します。
  3. クラウドプロバイダーのメカニズムを使用して、QA、ステージング、および本番環境の(秘密の)環境変数を定義します。
  4. 環境変数から自分自身を構成する単一のsettings.pyファイルを使用します。
  5. pydanticを使用して、環境変数を読み取り、チェックし、検証し、Django構成を定義するPython変数に型キャストします。

または、一部の開発者は、 settings_dev.pysettings_prod.pyなどの複数の設定ファイルを作成します。 残念ながら、このアプローチはうまく拡張できません。 これは、コードの重複、混乱、見つけにくいバグ、およびより高いメンテナンス作業につながります。

前述のベストプラクティスを使用すると、環境をいくつでも追加するのは簡単で、明確に定義されており、エラーが発生しません。 より複雑な環境構成を検討することもできますが、明確にするために、開発と本番の2つに焦点を当てます。

これは実際にはどのように見えますか?

Pydantic設定の管理と環境変数

ここでは、開発と本番の両方の例に焦点を当てます。 各環境が設定を異なる方法で構成する方法と、pydanticがそれぞれをサポートする方法を示します。

このサンプルアプリケーションにはDjangoがサポートするデータベースが必要なため、データベース接続文字列を保存する必要があります。 Pythonパッケージdj-database-urlを使用して、データベース接続構成情報を環境変数DATABASE_URLに移動します。 この変数はstr型であり、次のようにフォーマットされていることに注意してください。

 postgres://{user}:{password}@{hostname}:{port}/{database-name} mysql://{user}:{password}@{hostname}:{port}/{database-name} oracle://{user}:{password}@{hostname}:{port}/{database-name} sqlite:///PATH

開発環境では、使いやすさのためにDockerを含むPostgreSQLインスタンスを使用できますが、本番環境では、プロビジョニングされたデータベースサービスを指します。

定義したいもう1つの変数は、ブール値DEBUGです。 DjangoのDEBUGフラグは、実稼働環境では決してオンにしないでください。 開発中に追加のフィードバックを取得することを目的としています。 たとえば、デバッグモードでは、例外が発生するとDjangoは詳細なエラーページを表示します。

開発と本番の異なる値は、次のように定義できます。

変数名発達製造
DATABASE_URL postgres://postgres:mypw@localhost:5432/mydb postgres://foo1:foo2@foo3:5432/foo4
DEBUG True False

pydantic設定管理モジュールを使用して、環境に応じてこれらのさまざまな環境変数値のセットを管理します。

準備手順

これを実践するために、次のコンテンツを含む単一の.envファイルを作成して、開発環境の構成を開始します。

 DATABASE_URL=postgres://postgres:mypw@localhost:5432/mydb DEBUG=True

次に、 .envファイルをプロジェクトの.gitignoreファイルに追加します。 .gitignoreファイルは、機密情報をソース管理に保存することを回避します。

このアプローチは開発環境ではうまく機能しますが、本番環境の仕様では異なるメカニズムを使用します。 私たちのベストプラクティスは、本番環境変数が環境シークレットを使用することを示しています。 たとえば、Herokuでは、これらのシークレットはConfig Varと呼ばれ、Herokuダッシュボードを介して構成されます。 これらは、デプロイされたアプリケーションで環境変数として使用できるようになります。

ConfigVarsWebインターフェースのスクリーンショット。左側のサイドバーには、「構成変数によってアプリの動作が変更されます。独自のアドオンを作成するだけでなく、一部のアドオンには独自の機能が付属しています」という説明があります。メインセクションには2つの行があり、それぞれに2つのテキストフィールド、鉛筆アイコン、およびXアイコンがあります。テキストフィールドには、前の表の「変数名」列と「生産」列と同じデータがあります。右上隅には、「構成変数を非表示にする」というボタンがあります。

その後、アプリケーションの構成を調整して、いずれかの環境からこれらの値を自動的に読み取る必要があります。

Djangoのsettings.pyを構成する

この例に不可欠な構造を提供するために、新しいDjangoプロジェクトから始めましょう。 次のターミナルコマンドを使用して、新しいDjangoプロジェクトの足場を作成します。

 $ django-admin startproject mysite

これで、 mysiteプロジェクトの基本的なプロジェクト構造ができました。 プロジェクトのファイル構造は次のとおりです。

 mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py

settings.pyファイルには、アプリケーション構成の管理を可能にするボイラープレートコードが含まれています。 これには、関連する環境に合わせて調整する必要のある多くの事前定義されたデフォルト設定があります。

環境変数とpydanticを使用してこれらのアプリケーション設定を管理するには、次のコードをsettings.pyファイルの先頭に追加します。

 import os from pathlib import Path from pydantic import ( BaseSettings, PostgresDsn, EmailStr, HttpUrl, ) import dj_database_url # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent class SettingsFromEnvironment(BaseSettings): """Defines environment variables with their types and optional defaults""" DATABASE_URL: PostgresDsn DEBUG: bool = False class Config: """Defines configuration for pydantic environment loading""" env_file = str(BASE_DIR / ".env") case_sensitive = True config = SettingsFromEnvironment() os.environ["DATABASE_URL"] = config.DATABASE_URL DATABASES = { "default": dj_database_url.config(conn_max_age=600, ssl_require=True) } DEBUG = config.DEBUG

このコードは次のことを行います。

  • pydanticのBaseSettingsクラスから継承するクラスSettingsFromEnvironmentを定義します。
  • DATABASE_URLDEBUGを定義し、Pythonタイプヒントを使用してそれらのタイプとオプションのデフォルトを設定します。
  • システムの環境変数に存在しない場合、 .envファイル内の変数を検索するようにpydanticに指示するクラスConfigを定義します。
  • Configクラスをオブジェクトconfigにインスタンス化します。 必要な変数は、 config.DATABASE_URLおよびconfig.DEBUGとして使用できるようになります。
  • これらのconfigメンバーから通常のDjango変数DATABASESおよびDEBUGを定義します。

同じコードがすべての環境で実行され、pydanticが次の処理を行います。

  • 環境変数DATABASE_URLDEBUGを探します。
    • 本番環境のように環境変数として定義されている場合は、それらを使用します。
    • それ以外の場合は、 .envファイルからそれらの値を取得します。
    • 値が見つからない場合は、次のようになります。
      • DATABASE_URLの場合、エラーをスローします。
      • DEBUGの場合、デフォルト値のFalseが割り当てられます。
  • 環境変数が見つかると、フィールドタイプがチェックされ、どちらかが間違っているとエラーが発生します。
    • DATABASE_URLの場合、フィールドタイプがPostgresDsnスタイルのURLであることを確認します。
    • DEBUGの場合、フィールドタイプが有効で非厳密なpydanticブール値であることを確認します。

DATABASE_URLの構成値からのオペレーティングシステムの環境変数の明示的な設定に注意してください。 DATABASE_URLはすでに外部環境変数として定義されているため、 os.environ["DATABASE_URL"] = config.DATABASE_URLを設定するのは冗長に思えるかもしれません。 ただし、これにより、pydanticはこの変数を解析、チェック、および検証できます。 環境変数DATABASE_URLが欠落しているか、正しくフォーマットされていない場合、pydanticは明確なエラーメッセージを表示します。 これらのエラーチェックは、アプリケーションが開発環境から後続の環境に移行するときに非常に役立ちます。

変数が定義されていない場合は、デフォルトが割り当てられるか、エラーによって変数の定義を求められます。 生成されたプロンプトには、目的の変数タイプの詳細も示されます。 これらのチェックの副次的な利点は、新しいチームメンバーとDevOpsエンジニアが、定義する必要のある変数をより簡単に発見できることです。 これにより、すべての変数を定義せずにアプリケーションを実行した場合に発生する、見つけにくい問題を回避できます。

それでおしまい。 これで、アプリケーションには、settings.pyの単一バージョンを使用した保守可能な設定管理の実装がありsettings.py 。 このアプローチの利点は、 .envファイルまたはホスティング環境で利用できるその他の必要な手段で正しい環境変数を指定できることです。

スケーラブルパス

私はすべてのDjangoプロジェクトでpydanticランタイムタイピングを使用してDjango設定管理を使用しています。 私はそれがより小さく、より保守しやすいコードベースにつながることを発見しました。 また、新しい環境を追加するための、適切に構造化された自己文書化されたスケーラブルなアプローチも提供します。

このシリーズの次の記事は、Djangoアプリケーションをゼロから構築し、pydantic設定管理を使用して、Herokuにデプロイするためのステップバイステップのチュートリアルです。


Toptal Engineering Blogは、この記事で紹介されているコードサンプルをレビューしてくれたStephenDavidsonに感謝の意を表します。

この記事の以前のバージョンでは、特定のPythonリリースが強調されていました。 Pythonは数年前からタイプヒントをサポートしてきたため、このバージョン参照を削除することで紹介テキストを一般化しました。