Where to place .env files in Python projects and how to use them
What are .env files #
Environment variables are typically used to hold sensitive information, such as API keys, database passwords, and other configuration settings.
Python accesses environment variables through the os.getenv() or os.environ() functions.
import os
API_KEY = os.getenv('API_KEY')
When working on multiple projects, it can be difficult to manage environment variables. One common solution is to use a .env file to store environment variables. It is a text file that contains key-value pairs of environment variables.
API_KEY=1234567890
DATABASE_URL=postgres://user:password@localhost:5432/mydatabase
Access environment variables in Python with load_dotenv #
To load environment variables stored in a .env file, you can use the load_dotenv function from the dotenv package, then access the environment variables using os.getenv().
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv('API_KEY')
DATABASE_URL = os.getenv('DATABASE_URL')
Do not put .env files in the same directory as your Python script #
While it is common to place the .env file in the same directory as your Python script, this practice can lead to security risks. If you accidentally commit the .env file to a public repository, you expose your sensitive information to the world. You can use a .gitignore file to prevent the .env file from being committed, but mistakes can still happen, especially on the first commit.
Place .env files in parent directories #
A better practice is to store the .env file in a parent directory of your project. A level above the project directory that is not tracked by version control.
This way, you can keep your sensitive information separate from your codebase.
For example, if you have the following directory structure:
.
└── projects
├── projectA
│ └── main.py
├── projectB
│ └── main.py
└── .env
You can access the .env file from any of the projects by using the dotenv.load_dotenv() function.
The .env file is not restricted to the parent directory. It can be placed multiple levels above the project directory. So if you have a hierarchy of projects, you can place the .env file in a common directory that all projects can access.
.
└── backend
├── projectA
│ └── main.py
├── projectB
│ └── main.py
└── frontend
├── projectC
│ └── main.py
├── projectD
│ └── main.py
└── .env
Multiple .env files in parent directories #
If there are multiple .env files in the parent directory, and it’s parent directories, the load_dotenv() function will load the first .env file it finds starting from the current directory and moving up the directory tree.
For example if there is a .env file in dirb and dird directories, the load_dotenv() function will load the .env file in the dirb directory.
.
├── dirb
│ └── dirc
│ └── dird
│ ├── dire
│ │ └── main.py
│ └── .env
└── .env
Use find_dotenv() function for finding .env paths #
If you are unsure what .env file is being loaded, you can use the find_dotenv() function to find the .env file that is being loaded.
Without any arguments, the find_dotenv() function will search for a .env file starting from the current directory and moving up the directory tree. returning the path of the .env file that is being loaded.
Note the usecwd argument maybe required for some versions of Python to search the current directory first.
from dotenv import find_dotenv
dotenv_path = find_dotenv(usecwd=True)
print(dotenv_path)
# output
# /path/to/.env
Mutliple .env files for different environments #
A parameter to find_dotenv() can also be supplied to search for a specific .env file.
from dotenv import find_dotenv
dotenv_path = find_dotenv(filename='.env.testing')
print(dotenv_path)
# output
# /path/to/.env.testing
If you have multiple .env files for different environments, such as development, testing, and production, a combination of the find_dotenv() and load_dotenv() functions can be used to load the appropriate .env file.
from dotenv import find_dotenv, load_dotenv
dotenv_path = find_dotenv(filename='.env.testing')
load_dotenv(dotenv_path)
load_dotenv() called multiple times #
If you call the load_dotenv() function multiple times, it will not overwrite existing environment variables. This can lead to unexpected behavior if you are not aware of this limitation.
For example, if you have the following environment variables in a .env file:
API_KEY=prod12345
and another file called .env.testing with the following content
API_KEY=test12345
If you call the load_dotenv() function with the .env file first, then call it again with the .env.testing file, the API_KEY environment variable will still be prod12345.
from dotenv import load_dotenv
load_dotenv() # loads .env file
load_dotenv(find_dotenv(filename='.env.testing')) # loads .env.testing file
API_KEY = os.getenv('API_KEY')
print(API_KEY)
# output
# prod12345
If you want to overwrite existing environment variables, you can use the overwrite parameter of the load_dotenv() function.
from dotenv import load_dotenv
load_dotenv(overwrite=True) # loads .env file
load_dotenv(find_dotenv(filename='.env.testing'), overwrite=True) # loads .env.testing file
API_KEY = os.getenv('API_KEY')
print(API_KEY)
# output
# test12345