Last week, one of my Multiverse apprentices asked me if I knew how to load a
.env file in the Django web framework for Python. A
.env file is used to store environment variables for a project. These typically include settings such as database connection strings, API keys, or other configuration parameters. The purpose of a
.env file is twofold: to separate configuration from code, and to keep sensitive information out of version control systems such as Git. A
.env file is typically specified in a
.gitignore file to keep it untracked.
Here’s an example
dotenv npm package to load environment variables from a
.env file into
process.env. I assumed my apprentice needed something similar for Python, so I found some packages such as python-decouple and python-dotenv. However, my apprentice told me that their mentor sent them an article on freeCodeCamp which made me think they were supposed to do it in vanilla Python. I came up with a simple script that assumes the
.env file is in the current directory:
from pathlib import Path
path = Path(__file__).parent / ".env"
contents = path.read_text()
lines = contents.splitlines()
for line in lines:
key, val = line.split("=")
os.environ[key] = val
The script worked, but I was mistaken. My apprentice was supposed to load the environment variables into their current shell session before running the Django CLI commands. I knew that it was possible to set environment variables in a Unix shell (like Bash or Zsh) like so:
But frankly, I’m a noob when it comes to shell scripting, and I didn’t know about the dot command (
.). It evaluates the commands in a file as if they were entered directly into the terminal for the current shell session. There is an equivalent
source command in some Unix shells, but the dot command is the POSIX standard.
Let’s assume our current directory contains a
.env file with the following contents:
To evaluate the contents of the
.env file in our current shell session, we can run the following command:
If we try to print the value of the
API_KEY variable, we’ll see that it does exist:
echo $API_KEY # should print "abcdef123456"
Now let’s assume our current directory contains a
temp.py file with the following contents:
API_KEY = os.environ.get("API_KEY")
Let’s try to run the file:
Instead of seeing
"abcdef123456" printed out, we’ll see
None! Why is this? The
.env file doesn’t contain the
export keyword, which is a problem. There’s a difference between “regular” variables and environment variables.
Parent and child processes are a fundamental principle of Unix-like operating systems. A parent process initiates the creation of another process, known as a child process. A child process inherits environment variables, but not “regular” variables.
When we run
python3 ./temp.py, our current shell session is a parent process that spawns a child process. Because the
.env file does not contain the
export keyword, the
API_KEY variable was interpreted as a “regular” variable, not an environment variable, and is therefore unavailable to the child process.
The only standard way to export variables is to use the
export keyword. Because a
.env file does not usually contain the
export keyword, this isn’t really an option. However, common Unix shells such as Bash and Zsh provide the functionality to automatically export variables. We need to enable this functionality, evaluate the
.env file, then disable the functionality again.
In Bash, we can run
set -a to enable the
allexport option, and
set +a to disable it. It’s weird that
-a is used to enable the option and
+a is used to disable the option. I don’t know why that is. The semicolons are used to enter multiple commands on the same line.
set -a ; . ./.env ; set +a
In Zsh, we can run
setopt allexport to enable the
allexport option, and
unsetopt allexport to disable it. Again, the semicolons are used to enter multiple commands on the same line.
setopt allexport ; . ./.env ; unsetopt allexport
Assuming we are using Bash or Zsh, and we have run those commands, the
temp.py script should print out the value of the
API_KEY environment variable.
.envfile is used to store environment variables for a project. These typically include settings such as database connection strings, API keys, or other configuration parameters.
- The purpose of a
.envfile is to separate configuration from code and to keep sensitive information out of version control systems.
- The dot command (
.) evaluates the commands in a file as if they were entered directly into the terminal for the current shell session. There is an equivalent
sourcecommand in some Unix shells, but the dot command is standard.
- “Regular” variables are only available to the current shell session, which is a parent process. Environment variables are also available to child processes.
- In some Unix shells, variables can be automatically exported.
- To load the environment variables from a
.envfile into the current shell session, we need to enable automatic exports, evaluate the
.envfile, then disable automatic exports once more.