pydantic / pydantic-settings

Settings management using pydantic
https://docs.pydantic.dev/latest/usage/pydantic_settings/
MIT License
508 stars 50 forks source link

Unable to override loading of .env with .env.testing #282

Closed tedsecretsource closed 2 months ago

tedsecretsource commented 2 months ago

Without going into too much detail, is there a reason why I am unable to load a specific environment file if a .env file exists in the project? In other words…

Given I am in my testing environment and both a .env and a .env.testing files exist with valid entries When I load my settings using settings = Settings(_env_file=".env.testing") Then settings.app_env = 'testing'

However, settings.app_env = 'production' (because that is what is in the .env file).

What in the world am I missing? What could I possibly be doing wrong?

FWIW the actual use case is a bit more complex than this but this is the behavior that seems to be happening. Any pointers would be greatly appreciated 🙏

Ted

hramezani commented 2 months ago

Thanks @tedsecretsource for reporting the issue.

from the doc: If you need to load multiple dotenv files, you can pass multiple file paths as a tuple or list. The files will be loaded in order, with each file overriding the previous one.

Here is an example of passing multiple .env files:

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        # `.env.prod` takes priority over `.env`
        env_file=('.env', '.env.prod')
    )
tedsecretsource commented 2 months ago

Thanks @hramezani. Note that I'm not trying to load multiple files, just one file. I just want to load .env.testing. Are you suggesting that the way to do this would be something like: env_file=('.env', '.env.testing') so that, in production, where no .env.testing file exists, only .env would be loaded while on my local machine, where .env.testing does exist, it would override values in .env? If that's true, maybe that could work but the documentation also states pretty clearly:

settings = Settings(_env_file='prod.env', _env_file_encoding='utf-8')

but if I do the above replacing prod.env with .env.testing, .env always ends up loading and .env.testing never does.

You all need more context…

I'm doing this in FastAPI.

# File main.py (abbreviated)
def init_settings():
    return Settings(_env_file='.env.testing')

@app.post("/gpt/v1/q")
async def query_gpt(
    request: Request,
    settings: Annotated[Settings, Depends(init_settings)],
):
    """Query the index."""
    return {"settings": settings}

and then in the test:

app.dependency_overrides[init_settings] = lambda: Settings(_env_file='.env.testing')

def test_gpt_api_returns_200():
    response = client.post(
        url="/gpt/v1/q",
        data={"query": "What is Python?"},
    )
    assert response.status_code == 200

And the values in the settings dict are always from the .env file and not from .env.testing. I cannot for the life or me understand what I am doing wrong.

hramezani commented 2 months ago

with one file it should be ok. I didn't get the problem. if you include a file name in _env_file, pydantic-settings should only loads that file and use the values from the file.

If it doesn't work, probably you already have some variable on your system environment that overrides the value from the file.

BTW, please create an example code and let me know how to reproduce the problem.

tedsecretsource commented 2 months ago

probably you already have some variable on your system environment that overrides the value from the file

The output of env does not show any of the values in the .env.testing file, only normal server-related keys and values.

BTW, please create an example code and let me know how to reproduce the problem.

I really appreciate the offer. I was hoping to avoid this but I can't see any way around it :( I'll see what I can do. Thanks for the offer.

tedsecretsource commented 2 months ago

Problem solved!

We had this line in one of the included files (vestige of a prior version before using pydantic-settings):

load_dotenv(find_dotenv())

Naturally, that was putting the .env variables into the actual environment and overwriting anything we were sending in .env.testing… Live and learn 🤷

Thanks for your help and I certainly hope this helps someone else in the future!