Environment Variables
Environment Variables and Configuration
Why Do We Need This?
Imagine you’re building an app that connects to a database. You might write:
# BAD: Don't do this!
connection = connect_to_database(
host="localhost",
user="myuser",
password="supersecret123",
database="myapp"
)
Problems with this approach:
- Security Risk: Your password is visible in the code
- Hard to Change: Need to modify code to change settings
- Different Environments: Your local database is different from production
- Version Control: You’ll accidentally commit secrets to Git
The solution: Use environment variables and configuration files to keep secrets separate from code.
Environment Variables
What Are Environment Variables?
Environment variables are key-value pairs that exist in your operating system. They’re like global variables that any program can access.
# Setting an environment variable (in terminal)
export DATABASE_URL="postgresql://user:pass@localhost/db"
export API_KEY="sk-1234567890abcdef"
Reading Environment Variables in Python
import os
# Method 1: os.environ (will raise KeyError if missing)
database_url = os.environ['DATABASE_URL'] # Crashes if not set
# Method 2: os.getenv() (safer, can provide default)
database_url = os.getenv('DATABASE_URL') # Returns None if not set
api_key = os.getenv('API_KEY', 'default-key') # Returns 'default-key' if not set
Always prefer os.getenv()
because it won’t crash your program if the variable is missing.
Using .env Files
The Problem with System Environment Variables
Setting environment variables in your terminal is annoying:
export DATABASE_URL="..."
export API_KEY="..."
export DEBUG="True"
python app.py # Only works in this terminal session
Solution: .env Files
Create a .env
file in your project root:
# .env
DATABASE_URL=postgresql://user:password@localhost/myapp
API_KEY=sk-1234567890abcdef
DEBUG=True
MAX_CONNECTIONS=50
Loading .env Files
Install python-dotenv:
pip install python-dotenv
Use it in your code:
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Now you can access them normally
DATABASE_URL = os.getenv('DATABASE_URL')
API_KEY = os.getenv('API_KEY')
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
print(f"Connecting to: {DATABASE_URL}")
print(f"Debug mode: {DEBUG}")
Important: Add .env to .gitignore
Never commit .env files to version control!
Create or update your .gitignore
:
# Environment variables
.env
.env.local
.env.production
# Other common ignores
__pycache__/
*.pyc
.vscode/
.DS_Store
Configuration Files
What Goes Where?
Use .env
for:
- Secrets (passwords, API keys, tokens)
- Environment-specific settings (database URLs, debug flags)
- Things that change between development/production
Use config files for:
- Application constants
- Non-secret settings
- Default values
- Lists and complex data
Simple Config with Python Files
Create a config.py
file:
# config.py
"""Application configuration."""
# Application settings
DEFAULT_PAGE_SIZE = 20
MAX_FILE_SIZE_MB = 10
ALLOWED_FILE_TYPES = ['.jpg', '.png', '.pdf', '.txt']
# Cache settings
CACHE_TIMEOUT = 300 # 5 minutes in seconds
# API settings
REQUEST_TIMEOUT = 30 # seconds
MAX_RETRIES = 3
# Feature flags
ENABLE_EMAIL_NOTIFICATIONS = True
ENABLE_FILE_UPLOAD = True
Using the config:
# app.py
import config
print(f"Page size: {config.DEFAULT_PAGE_SIZE}")
print(f"Allowed files: {config.ALLOWED_FILE_TYPES}")
if config.ENABLE_EMAIL_NOTIFICATIONS:
print("Email notifications are enabled")