使用文件强制转换和输入环境变量

pir*_*pir 7 python typing environment-variables visual-studio-code

对于我的所有项目,我在开始时加载所有 env 变量,并检查所有预期的键是否存在,如.env.example遵循dotenv-safe 方法的文件所述。

然而,env 变量是字符串,每当它们在 Python 代码中使用时都必须手动转换。这很烦人且容易出错。我想使用.env.example文件中的信息来转换 env 变量并在我的 IDE (VS Code) 中获得 Python 输入支持。我怎么做?

环境示例

PORT: int
SSL: boolean
Run Code Online (Sandbox Code Playgroud)

Python 理想行为

# Set the env in some way (doesn't matter)
import os
os.environment["SSL"] = "0"
os.environment["PORT"] = "99999"

env = type_env()
if not env["SSL"]: # <-- I'd like this to be cast to boolean and typed as a boolean
    print("Connecting w/o SSL!")
if 65535 < env["PORT"]:  # <-- I'd like this to be cast to int and typed as an int
    print("Invalid port!")
Run Code Online (Sandbox Code Playgroud)

在此代码示例中,type_env()假设函数仅支持、、 和boolean,该函数会是什么样子?intfloatstr

/sf/answers/824696281/所示进行转换并不太难,但我不清楚如何让它与打字支持一起工作。

rus*_*ro1 11

我会建议使用pydantic

来自 StackOverflow pydantic 标签信息

Pydantic 是一个基于 Python 类型提示(PEP484)和变量注释(PEP526)的数据验证和设置管理库。它允许在 Python 中为复杂结构定义模式。

让我们假设您有一个包含您SSLPORTenvs的文件:

with open('.env', 'w') as fp:
    fp.write('PORT=5000\nSSL=0')
Run Code Online (Sandbox Code Playgroud)

那么你可以使用:

from pydantic import BaseSettings

class Settings(BaseSettings):
    PORT : int
    SSL : bool
    class Config:
        env_file = '.env'

config = Settings()

print(type(config.SSL),  config.SSL)
print(type(config.PORT),  config.PORT)
# <class 'bool'> False
# <class 'int'> 5000
Run Code Online (Sandbox Code Playgroud)

使用您的代码:

env = Settings()

if not env.SSL:
    print("Connecting w/o SSL!")
if 65535 < env.PORT: 
    print("Invalid port!")
Run Code Online (Sandbox Code Playgroud)

输出:

Connecting w/o SSL!
Run Code Online (Sandbox Code Playgroud)


Kev*_*sco 5

以下解决方案提供了对所需类型的运行时转换编辑器的类型提示帮助,而 无需使用外部依赖项

还可以查看kederrac 的答案以获取使用 的绝佳替代方案pydantic,它会为您处理所有这些问题。


直接使用非 Python 的 dotenv 文件会太难,如果不是不可能的话。处理某些 Python 数据结构中的所有信息更容易,因为这让类型检查器无需任何修改即可完成其工作。

我认为要走的路是使用Python dataclasses。请注意,虽然我们在定义中指定了类型,但它们仅用于类型检查器,而不是在运行时强制执行。这是环境变量的问题,因为它们string基本上是外部映射信息。为了克服这个问题,我们可以__post_init__方法中强制进行强制转换

执行

首先,出于代码组织的原因,我们可以创建一个具有类型强制逻辑的 Mixin。请注意,这种bool情况很特殊,因为它的构造函数将输出True任何非空字符串,包括"False". 如果您想处理其他一些非内置类型,您也需要为它添加特殊处理(尽管我不建议让这个逻辑处理比这些简单类型更多)。

import dataclasses
from distutils.util import strtobool

class EnforcedDataclassMixin:

    def __post_init__(self):
        # Enforce types at runtime
        for field in dataclasses.fields(self):
            value = getattr(self, field.name)
            # Special case handling, since bool('False') is True
            if field.type == bool:
                value = strtobool(value)
            setattr(self, field.name, field.type(value))
Run Code Online (Sandbox Code Playgroud)

这个实现也可以用装饰器来完成,见这里

然后,我们可以.env.example像这样创建一个“ ”文件的等价物:

import dataclasses

@dataclasses.dataclass
class EnvironmentVariables(EnforcedDataclassMixin):
    SSL: bool
    PORT: int
    DOMAIN: str
Run Code Online (Sandbox Code Playgroud)

为了便于解析os.environ,我们可以创建一个函数,如

from typing import Mapping

def get_config_from_map(environment_map: Mapping) -> EnvironmentVariables:
    field_names = [field.name for field in dataclasses.fields(EnvironmentVariables)]
    # We need to ignore the extra keys in the environment,
    # otherwise the dataclass construction will fail.
    env_vars = {
        key: value for key, value in environment_map.items() if key in field_names
    }
    return EnvironmentVariables(**env_vars)

Run Code Online (Sandbox Code Playgroud)

用法

最后,把这些东西放在一起,我们可以写在一个设置文件中:

import os
from env_description import get_config_from_map


env_vars = get_config_from_map(os.environ)

if 65535 < env_vars.PORT:
    print("Invalid port!")

if not env_vars.SSL:
    print("Connecting w/o SSL!")
Run Code Online (Sandbox Code Playgroud)

静态类型检查在 VS Code 和 mypy 中正常工作。如果您将PORT(它是int)分配给类型为 的变量str,您将收到警报!

类型提示工作

为了假装它是一个字典,Python 提供asdictdataclasses模块中的方法。

env_vars_dict = dataclasses.asdict(env_vars)
if 65535 < env_vars_dict['PORT']:
    print("Invalid port!")
Run Code Online (Sandbox Code Playgroud)

但遗憾的是(截至本回答的时间),您失去了静态类型检查支持。mypy似乎正在进行中

  • 啊。不知道自动奖励奖励会减半积分。对于那个很抱歉! (2认同)