使用类型提示简化 Django 设置:Pydantic 教程

已发表: 2022-07-22

Django 项目曾经让我感到沮丧,因为我缺乏一种强大且可扩展的方式来添加新环境。 通过将 pydantic 和 Python 类型提示结合在一起,我建立了我需要的强大基础。

如 PEP 484 中所述,类型提示支持静态分析,但这些相同的注释在运行时也可用。 像 pydantic 这样的第三方包提供了使用这些额外元数据的运行时类型检查。 Pydantic 使用 Python 类型提示来帮助管理设置元数据并执行运行时数据验证。

本 pydantic 教程将展示在 Django 中使用 pydantic 设置管理的深远而积极的影响。

我们的配置遵循 Twelve-Factor App 网站上描述的最佳实践:

  1. 将非常量和秘密配置定义为环境变量。
  2. 在开发环境中,在.env文件中定义环境变量并将.env添加到.gitignore
  3. 使用云提供商的机制为 QA、暂存和生产环境定义(秘密)环境变量。
  4. 使用从环境变量配置自身的单个settings.py文件。
  5. 使用 pydantic 读取、检查、验证和类型转换环境变量到定义 Django 配置的 Python 变量。

或者,一些开发人员创建多个设置文件,例如settings_dev.pysettings_prod.py 。 不幸的是,这种方法不能很好地扩展。 它会导致代码重复、混乱、难以发现的错误和更高的维护工作量。

使用上述最佳实践,添加任意数量的环境很容易、定义明确且防错。 尽管我们可以探索更复杂的环境配置,但为了清楚起见,我们将重点关注两个:开发和生产。

这在实践中是什么样的?

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 实例以方便使用,而在我们的生产环境中,我们将指向一个预置的数据库服务。

我们要定义的另一个变量是布尔值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 Vars,并通过 Heroku Dashboard 进行配置。 它们作为环境变量提供给已部署的应用程序:

Config Vars Web 界面的屏幕截图。左侧边栏有一个描述:“配置变量改变了你的应用程序的行为方式。除了创建你自己的,一些附加组件带有它们自己的。”主要部分有两行,每行有两个文本字段、一个铅笔图标和一个 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

此代码执行以下操作:

  • 定义一个类SettingsFromEnvironment ,继承自 pydantic 的BaseSettings类。
  • 定义DATABASE_URLDEBUG ,使用 Python 类型提示设置它们的类型和可选默认值。
  • 定义一个类Config告诉 pydantic 如果不存在于系统的环境变量中,则在.env文件中查找变量。
  • Config类实例化为对象config ; 所需的变量可作为config.DATABASE_URLconfig.DEBUG
  • 从这些config成员中定义常规的 Django 变量DATABASESDEBUG

相同的代码在所有环境中运行,pydantic 负责以下工作:

  • 它查找环境变量DATABASE_URLDEBUG
    • 如果定义为环境变量,就像在生产中一样,它将使用那些。
    • 否则,它会从.env文件中提取这些值。
    • 如果它没有找到值,它将执行以下操作:
      • 对于DATABASE_URL ,它会引发错误。
      • 对于DEBUG ,它分配一个默认值False
  • 如果它找到一个环境变量,它将检查字段类型并在其中任何一个错误时给出错误:
    • 对于DATABASE_URL ,它验证其字段类型是PostgresDsn样式的 URL。
    • 对于DEBUG ,它验证其字段类型是有效的、非严格的 pydantic 布尔值。

请注意DATABASE_URL配置值中操作系统环境变量的显式设置。 设置os.environ["DATABASE_URL"] = config.DATABASE_URL似乎是多余的,因为DATABASE_URL已经定义为外部环境变量。 但是,这允许 pydantic 解析、检查和验证这个变量。 如果环境变量DATABASE_URL丢失或格式不正确,pydantic 将给出明确的错误消息。 随着应用程序从开发转移到后续环境,这些错误检查非常宝贵。

如果未定义变量,则将分配默认值或将提示错误以提示对其进行定义。 任何生成的提示也会详细说明所需的变量类型。 这些检查的一个附带好处是新团队成员和 DevOps 工程师更容易发现需要定义哪些变量。 这避免了当应用程序在未定义所有变量的情况下运行时导致的难以发现的问题。

而已。 该应用程序现在使用单个版本的settings.py具有可维护的设置管理实现。 这种方法的美妙之处在于它允许我们在.env文件中指定正确的环境变量或通过我们的托管环境提供的任何其他所需方式。

可扩展的路径

我一直在我的所有 Django 项目中使用带有 pydantic 运行时类型的 Django 设置管理。 我发现它会导致更小、更可维护的代码库。 它还提供了一种结构良好、自记录且可扩展的方法来添加新环境。

本系列的下一篇文章是关于从头开始构建 Django 应用程序的分步教程,使用 pydantic 设置管理,并将其部署到 Heroku。


Toptal 工程博客感谢 Stephen Davidson 审阅本文中提供的代码示例。

本文的早期版本强调了特定的 Python 版本。 由于 Python 多年来一直支持类型提示,因此我们通过删除此版本参考来概括介绍性文本。