Mastering Data Security in Python: A Deep Dive into .env Files and python-dotenv
Tags: programming, python, security, coding
Published: 2023-08-09 11:35:20


When it comes to managing sensitive information such as API keys, passwords, and access tokens, developers must be cautious to avoid exposing this crucial data in their codebase. One common bad practice is hardcoding sensitive data directly into the source code, leaving it vulnerable to accidental exposure or malicious attacks.
To address this issue, the Python community has been kind enough to develop various third-party packages to help with loading environment variables from a .env file, and one such package is often called python-dotenv. It's a popular choice for this purpose, allowing Python developers to utilize .env files to store sensitive information separately and securely.
In this blog post, we will explore the significance of .env files and their importance in securing confidential information within Python projects. *Additionally, you can find the complete source code available on GitHub.*

## What are .env files?
.env files are simple text files that store environment variables. Environment variables are dynamic values that can affect how processes run on a computer system. They are used to store configuration settings, paths, and other important information. These files follow a key-value pair format, with each line representing a unique variable, like this:

```
API_KEY=my_api_key
SECRET_KEY=my_secret_key
```
When you're setting an environment variable to store a string value, you generally don't need to use quotation marks. The value you provide will be treated as a string by default. However, if the string contains spaces or special characters that might be misinterpreted, using quotation marks can help ensure the variable is interpreted correctly

## Differentiating Environmental Variables from Regular Variables
To clear the air, variables and environment variables are different concepts in programming. We'll be using these two terms as we proceed. Variables are used within a specific program's code to store temporary data, while environment variables are used to store configuration and shared data that can be accessed by multiple programs or processes on an operating system. We can also think of regular variables as boxes where we keep our toys, and environment variables as a special magic box where we keep important information that many people can use.


## How to Use .env files in Python with python-dotenv:

Step 1: Install the `python-dotenv` library:
```
pip install python-dotenv
```

Step 2: Create a .env file in your project's root directory and add your sensitive variables in the key-value format mentioned earlier.
```plaintext
DATABASE_HOST=189:0:0:3
DATABASE_USER=thedataisaac
MAIL=${DATABASE_USER}@icloud.com
API_KEY=abcd1234efgh5678ijklmnop
```
The "MAIL" variable is constructed using the ${DATABASE_USER}@icloud.com syntax. When this configuration file is read by your application or system, the value of the environment variable will be substituted into the configuration settings, resulting in a dynamic configuration based on the actual environment variable values.

Step 3: Load the .env file in your Python script using "python-dotenv":
```python
import os
from dotenv import load_dotenv

load_dotenv()  # Load variables from .env file into the environment

database_host = os.getenv("DATABASE_HOST")
database_user = os.getenv("DATABASE_USER")
mail=os.getenv("MAIL")
api_key=os.getenv("API_KEY")

print(f"Database Host: {database_host}")
print(f"Database User: {database_user}")
print(f"Mail: {mail}")
print(f"API Key: {api_key}")
```
The load_dotenv() function loads environment variables from a file named .env in the current directory or another specified location. If no argument is passed to the load_dotenv() function, it will by default search for a file named .env in the current directory. But If you have multiple .env files in the same directory and want to load a specific one using the load_dotenv() function, you can pass the filename as an argument to the function. 
Here's how you can load the appropriate environment variables:
```python
load_dotenv("sample.env")
```

## Best Practices with .env files:

- Add .env to .gitignore so it does not get committed to GitHub repository. If you want others to know that they should have a .env file, you can create a template.env file and write in it the variable names leaving the variables empty.
```
DATABASE_HOST=
DATABASE_USER=
MAIL=
API_KEY=
```
- Avoid giving a regular variable and an environment variable the same name to prevent confusion. When an environment variable has the same name with a regular variable, if the override parameter in the load_dotenv() is set to True, the values specified in the .env file will overwrite any existing environment variables with the same name.
```python
load_dotenv(override=True)
```



## Using dotenv_values 

You can use the `dotenv_values` function in Python to get your environment variable in form of a python dictionary. You need to first import the `dotenv_values` function alongside `load_dotenv`. You can do that by writing;

```python
from  dotenv import load_dotenv, dotenv_values
config=dotenv_values(".env")
print(config["API_KEY"])
```
The dotenv_values function loads the environment variables from the .env file, and converts them into a dictionary. The dictionary is then assigned to the variable "config". You can then access individual values from the dictionary using their keys.

## Using .env.shared and .env.secret
Another special thing you can do with this library is to use `.env.shared` and `.env.secret` files. The ".env.secret" file should contain sensitive information and secret keys that should not be shared publicly, and the ".env.shared" file can contain environment variables that are safe to share and can be committed to version control. 
Here's how you can use this:

```python
from dotenv import dotenv_values

# Load environment variables from .env.secret and .env.shared
config = {
    **dotenv_values(".env.secret"),
    **dotenv_values(".env.shared")
}

# Access individual variables
print(config["DATABASE_PASSWORD"])
print(config["SECRET_API_KEY"])
print(config["DATABASE_HOST"])
print(config["API_KEY"])
```
The "**" operator is called dictionary unpacking. It's a convenient way to merge the contents of two or more dictionaries into a single dictionary. 

## Conclusion
In this blog post, we learned how to secure sensitive data in Python projects with .env files and python-dotenv. We saw how .env files can help us store environment variables separately and securely, how environment variables differ from regular variables, and how to use python-dotenv to load and access the variables in our Python script. By following these steps, you can improve the security and maintainability of your Python projects.