如何在测试期间覆盖“env_file”?

问题描述 投票:0回答:5

我正在从我的

config.py
: 中的 .prod.env

文件中读取环境变量
from pydantic import BaseSettings


class Settings(BaseSettings):
    A: int

    class Config:
        env_file = ".prod.env"
        env_file_encoding = "utf-8"

settings = Settings()

在我的 main.py 中,我正在创建

app
,如下所示:

from fastapi import FastAPI
from app.config import settings

app = FastAPI()
print(settings.A)

我可以在我的

conftest.py
中覆盖这样的设置变量:

import pytest
from fastapi.testclient import TestClient

from app.main import app
from app.config import settings

settings.A = 42

@pytest.fixture(scope="module")
def test_clinet():
    with TestClient(app) as client:
        yield client

这很好用,每当我使用

settings.A
时我都会得到42。

但是是否可以将整个

env_file
.prod.env
覆盖到另一个环境文件
.test.env

另外,我可能想在导入

settings.A = 42
之前在 conftest.py 中调用
app
,对吗?

python pytest fastapi pydantic
5个回答
2
投票

您可以通过使用

Settings
关键字参数创建
_env_file
实例来覆盖您使用的 env 文件。

来自文档

在实例化时通过

_env_file
关键字参数传递文件路径(方法 2)将覆盖
Config
类上设置的值(如果有)。如果结合使用上述代码片段,则会加载 prod.env,而 .env 将被忽略。

例如,这应该适用于您的测试 -

import pytest
from fastapi.testclient import TestClient

import app.config as conf
from app.config import Settings

# replace the settings object that you created in the module
conf.settings = Settings(_env_file='.test.env')

from app.main import app

# just to show you that you changed the module-level
# settings
from app.config import settings

@pytest.fixture(scope="module")
def test_client():
    with TestClient(app) as client:
        yield client

def test_settings():
    print(conf.settings)
    print(settings)

您可以创建一个

.test.env
,设置
A=10000000
,然后使用

运行
pytest -rP conftest.py

# stuff
----- Captured stdout call -----
A=10000000
A=10000000

这看起来有点混乱(尽管这可能只用于测试目的),所以我建议不要创建一个可以被代码中的所有内容导入的

settings
对象,而是让它成为你创建的东西,比如说,您的
__main__
实际创建并运行应用程序,但这是您需要做出的设计选择。


0
投票

我今天也遇到了同样的问题,真的很烦人。我的目标是为单元测试设置不同的 postgresql 数据库。默认配置来自

.env
文件。但仔细想一想,也就不难理解了。这一切都归结为
conftest.py
中导入模块的顺序。我基于@wkl的答案来给出下面的例子:

import pytest

from typing import Generator
from fastapi.testclient import TestClient

import app.core.config as config
from app.core.config import Settings

# Replace individual attribute in the settings object
config.settings = Settings(
    POSTGRES_DB="test_db")

# Or replace the env file in the settings object
config.settings = Settings(_env_file='.test.env')

# All other modules that import settings are imported here
# This ensures that those modules will use the updated settings object
# Don't forget to use "noqa", otherwise a formatter might put it back on top
from app.main import app  # noqa
from app.db.session import SessionLocal  # noqa


@pytest.fixture(scope="session")
def db() -> Generator:
    try:
        db = SessionLocal()
        yield db
    finally:
        db.close()


@pytest.fixture(scope="module")
def client() -> Generator:
    with TestClient(app) as c:
        yield c

0
投票

就今天而言,BaseSettings 已移至 pydantic-settings。

我今天遇到了同样的问题(pydantic-settings==2.2.1)。 对我有用的是:

Settings.model_config.update(env_file=".test.env")

0
投票

Pydantic Settings 类读取 .env 文件中指定的环境变量以及当前会话环境中找到的环境变量。如果两者都存在一个值,它将选择从环境中读取。您可以利用这一事实来覆盖测试期间在 env 文件中找到的值。

如果您在

pytest_configure
中包含
/conftest.py
,您可以在进行任何导入之前运行代码。这允许您在初始化设置之前覆盖环境变量。环境变量的改变只影响python进程。

from pathlib import Path
from dotenv import load_dotenv

def pytest_configure(config):
    # This is run before any imports allowing us to inject
    # dependencies via environment variables into Settings
    # This just affects the variables in this process's environment

    # Find the .env file for the test environment
    test_env = str(Path(__file__).parent / "test.env")
    # Load the environment variables and overwrite any existing ones
    load_dotenv(test_env, override=True)

-1
投票

我发现的一个解决方法是从 Config 中完全删除

env_file
并将其功能替换为 dotenv 中的
load_dotenv()
,如下所示:

config.py

from pydantic import BaseSettings
from dotenv import load_dotenv

load_dotenv(".prod.env")


class Settings(BaseSettings):
    A: int


settings = Settings()

conftest.py

import pytest
from fastapi.testclient import TestClient
from dotenv import load_dotenv

load_dotenv("test.env")

from app.config import settings
from app.main import app


@pytest.fixture(scope="module")
def test_clinet():
    with TestClient(app) as client:
        yield client

请注意,调用

load_dotenv("test.env")
发生在导入设置之前 (
from app.config import settings
)

还要注意 load_dotenv() 将为整个 python 脚本全局加载环境变量。

像这样加载环境变量不会覆盖已经导出的变量,与在 pydantic 的 BaseSettings 中使用

env_file
相同

© www.soinside.com 2019 - 2024. All rights reserved.