Add --env-file flag to the Python CLI

Add --env-file flag to the Python CLI

Summary

Proposal: Add a new command-line flag --env-file to the Python interpreter.
Purpose: To allow developers to load environment variables from a file (e.g., .env) directly at runtime, simplifying local development and configuration management.

Motivation

In modern software development, environment variables are the standard way to manage configuration. In production environments (e.g., Docker containers, cloud platforms), these variables are typically injected by the host system.

However, during local development, developers often need to manually load these variables from a file. Currently, this requires the use of third-party libraries such as python-dotenv or load-dotenv, which adds an extra dependency and a boilerplate line of code to every project.

This proposal aims to bridge that gap by introducing a native, zero-dependency solution directly in the Python CLI, making the development workflow more consistent and convenient.

Proposed Implementation

The new flag would be used as follows:

python main.py --env-file .env

Behavior:

  1. Loading Mechanism: When the --env-file flag is present, the Python interpreter should read the specified file before executing the script.
  2. File Format: The file should follow the standard KEY=VALUE format (one variable per line).
  3. Environment Injection: All variables defined in the file should be loaded into the process’s environment (os.environ).
  4. Precedence: Variables loaded from the file should not overwrite existing environment variables set in the host shell, unless explicitly designed to do so.
  5. Error Handling: If the specified file does not exist or is unreadable, the interpreter should display a clear error message and exit with a non-zero status code.
  6. Precedent: This behavior is already widely adopted and familiar to developers from other ecosystems, such as Docker (--env-file) and Node.js (dotenv).

Example

File: .env

DATABASE_URL=postgresql://user:password@localhost/dbname
DEBUG=True
SECRET_KEY=your_secret_key_here

File: main.py

import os

def main():
    print(f"Debug mode: {os.getenv('DEBUG')}")
    print(f"Database URL: {os.getenv('DATABASE_URL')}")

if __name__ == "__main__":
    main()

Execution:

python main.py --env-file .env

Output:

Debug mode: True
Database URL: postgresql://user:password@localhost/dbname

Benefits

  • Simplified Development Workflow: Eliminates the need for boilerplate code and external libraries for loading configuration during development.
  • Consistency: Aligns Python’s command-line interface with established patterns in other popular tools (Docker, Node.js), making it more intuitive for developers.
  • Zero Dependencies: Allows small scripts and projects to use environment-based configuration without adding extra packages to their requirements.
  • Improved Onboarding: New developers can start a project by simply running a single command with a provided .env file.

Conclusion

Adding a --env-file flag to the Python interpreter is a small but impactful quality-of-life improvement that would significantly enhance the developer experience for a vast number of Python projects. It promotes best practices in configuration management while reducing friction in local development environments.

Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:No response

How would that ‘unless’ be specified? In python-dotenv, it’s an explicit flag (override).

1 Like

Why not just source the env file before running python?

8 Likes

The tradition solution is to use a shell wrapper to set the env then run the command. Works on windows, mac and unix.

Why not in this case?

3 Likes

I wouldn’t mind native support in Python for the envfile/dot-env convention, but this particular proposal has some significant flaws.

By way of example:

That won’t work. Arguments after a script get passed to that script as part of sys.argv.

I also don’t see any consideration of what the parsed types of values should be. Maybe they should all be strings but that needs to be an explicit decision, not something I guess by reading the idea.
EDIT: on second thought, I guess putting them into os.environ heavily hints that they’re strings.

I won’t go over every line, as I don’t think it’s a good use of everyone’s time.
The biggest issue, however, is worth mentioning. There’s no mention of any existing PyPI packages which provide this functionality (I believe there are several?) and explanation of why those packages are not sufficient for everyone’s needs.

3 Likes

“Standard library only” is a great thing to be able to brag, but ISTM most feature requests that involve this don’t actually justify it in any way. Considering that most of the packages on PyPI are very freely licensed and often not GPL (I grabbed the first one that came up in a search for dotenv and it’s BSD 3-clause), there’s little reason to demand that everything be in the core - if you’re in a situation where you absolutely need to do everything behind an air gap, vendor one of those packages into your own project and take responsibility for it. Though personally, I wouldn’t do that; those packages generally are much more complex than any single project needs, due to supporting many use-cases, so it’d be easier to roll your own.

(And of course, MOST of the time, pip install X is a perfectly fine way to get something.)

1 Like

Do you really need anything more than?

for key, value in re.findall(r"^(\w+)=(.*)", pathlib.Path(".env").read_text(), flags=re.M):
    os.environ.setdefault(key, value)

FWIW, CLI options to python.exe are a conceptual pain for embedders where there is no python.exe.

1 Like

I don’t know about such a solution (I’m not a very experienced developer), can you tell me more?

Yes, that’s an option, but I think it would be cool to be able to do this when running a script, like ‘python --env-file .env main.py’

I’ve been working as a Python developer for a little over a year, and yet, while developing, I constantly forget that simply adding the .env file next to main.py isn’t enough, you also need to load these variables somehow. :grimacing:

This is an elegant solution for loading environment variables without even using third-party libraries! I’ll copy your example to use in the projects I’m working on.
However, sometimes I encountered the problem of needing to load environment variables before the main code, that is, before imports (since the imports already used variables, without which the application would break). Perhaps this is an application architecture issue, but I still think it would be great to be able to launch a Python application with the necessary environment variables right away, without adding code that isn’t needed in production.

I completely forgot about the attributes that come after main.py. May be it possible to run this way? ‘python --env-file .env’

In my opinion, third-party libraries are insufficient to meet everyone’s needs because:

  • They require writing code that isn’t needed in production
  • They add unnecessary dependencies that are only needed for development
  • Sometimes, variable loading functions need to be called before imports, which doesn’t comply with the pep standard
. .env
python main.py
4 Likes

On unix (mac, linux, *bsd etc) I’d write something like this for your example:

#!/bin/bash
export DATABASE_URL=postgresql://user:password@localhost/dbname
export DEBUG=True
export SECRET_KEY=your_secret_key_here
cd /your_app_folder
python main.py

You would put these lines in a file and set the file executable (chmod +x ~/bin/db-app).

On Windows it would be a script in a .cmd file say db-app.cmd

@echo off
setlocal
set DATABASE_URL=postgresql://user:password@localhost/dbname
set DEBUG=True
set SECRET_KEY=your_secret_key_here
cd \your-app-folder
py main.py
endlocal

Preparing a runtime environment is a much broader term than just setting env variables (e.g. on Linux it can also include capabilities, cgroup limits, etc.). I see no logic in splitting this task and doing some parts before starting the interpreter and other parts in the interpreter itself.


It was not specified yet if the .env file could contain variables intended for the Python itself like PYTHONHOME.

3 Likes

But this way you have to create a custom script for *nix and windows, and probably also ship a .env for other uses. It’s not DRY.

As Charles and Serhiy pointed out, you can source the env file, but this works only on *nix.

On ClickPy, python-dotenv is ranked #92 and it’s mainly used on Linux. And it’s a package that’s not a 4-line function.

I think anyway the request is premature, since Python doesn’t have a dotenv module :wink:

1 Like

Notice that with the script approach can also change directory.
I did not show it but you also have the script use venv etc, etc.

*nix and Windows only requires 2 scripts if your shell scripting is up to the cross platform issues.

Just to note: there are subtle differences between how things like dotenv handles quoting and how POSIX shells handle quoting. You should not, in general, source .env like this.

Yeah, of course. You can do a lot of things with a script. But setting env vars? If you have only a bunch of them, it’s simple. But if you created a .env file, probably you don’t have just a bunch of 'em :wink:

I suppose the only way a dotenv module will be included in the stdlib is creating a PEP. Not something I’m interested in, and I’m not so skilled to do it. And I suppose the OP is not so skilled too.

So I suggest him to just use the PyPI package and live with it :wink:

1 Like