Python Configuration
Configuration management is a critical aspect of Python application development, especially when moving from development to production environments. In this guide, we'll explore various methods to handle configuration in Python applications, helping you choose the right approach for your projects.
Introduction to Configuration Management
Configuration management refers to the process of separating code from configuration settings like database credentials, API keys, feature toggles, and environment-specific parameters. Good configuration practices help you:
- Keep sensitive data out of your code repository
- Easily deploy to different environments
- Change application behavior without code modifications
- Follow the 12-Factor App methodology
Let's explore the various approaches to configuration in Python, from simple to more sophisticated options.
Simple Config Files
The most straightforward approach is to use a dedicated Python file for configuration.
Using Python Files
Creating a config.py
file is the simplest approach:
# config.py
DATABASE_HOST = "localhost"
DATABASE_PORT = 5432
DATABASE_NAME = "myapp"
DATABASE_USER = "user"
DATABASE_PASSWORD = "password" # Not recommended for production
DEBUG = True
LOG_LEVEL = "INFO"
Then in your application code:
import config
def connect_to_db():
print(f"Connecting to {config.DATABASE_NAME} on {config.DATABASE_HOST}...")
# Connection logic here
# Example usage
connect_to_db()
Output:
Connecting to myapp on localhost...
Environment-Specific Configuration
For different environments, you can extend this approach:
# config.py
import os
# Default configuration (development)
DATABASE_HOST = "localhost"
DATABASE_PORT = 5432
DATABASE_NAME = "myapp_dev"
DEBUG = True
# Override with production settings
if os.getenv("ENVIRONMENT") == "production":
DATABASE_HOST = "production-db.example.com"
DATABASE_NAME = "myapp_prod"
DEBUG = False
INI Configuration Files
Python's built-in configparser
module allows you to use INI-style configuration files.
Basic configparser Example
First, create an INI file:
; config.ini
[database]
host = localhost
port = 5432
name = myapp
user = user
password = password
[app]
debug = true
log_level = INFO
Then read it in your Python code:
import configparser
def load_config(config_file="config.ini"):
config = configparser.ConfigParser()
config.read(config_file)
return config
# Example usage
config = load_config()
db_name = config['database']['name']
debug_mode = config['app'].getboolean('debug')
print(f"Database name: {db_name}")
print(f"Debug mode: {debug_mode}")
Output:
Database name: myapp
Debug mode: True
JSON Configuration
JSON provides another common format for configuration files:
import json
def load_config(config_file="config.json"):
with open(config_file, 'r') as f:
return json.load(f)
# Example usage
try:
config = load_config()
print(f"Database name: {config['database']['name']}")
print(f"Debug mode: {config['app']['debug']}")
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Error loading configuration: {e}")
A sample config.json
file would look like:
{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp",
"user": "user",
"password": "password"
},
"app": {
"debug": true,
"log_level": "INFO"
}
}
YAML Configuration
YAML offers a more human-readable format for configuration files. You'll need to install the PyYAML package:
pip install pyyaml
import yaml
def load_config(config_file="config.yaml"):
with open(config_file, 'r') as f:
return yaml.safe_load(f)
# Example usage
try:
config = load_config()
print(f"Database name: {config['database']['name']}")
print(f"Debug mode: {config['app']['debug']}")
except (FileNotFoundError, yaml.YAMLError) as e:
print(f"Error loading configuration: {e}")
A sample config.yaml
file:
database:
host: localhost
port: 5432
name: myapp
user: user
password: password
app:
debug: true
log_level: INFO
Environment Variables
Following the 12-Factor App methodology, environment variables are an excellent way to configure applications, especially in containerized environments.
Basic Environment Variables
import os
# Load configuration from environment variables
DATABASE_HOST = os.getenv("DATABASE_HOST", "localhost")
DATABASE_PORT = int(os.getenv("DATABASE_PORT", "5432"))
DATABASE_NAME = os.getenv("DATABASE_NAME", "myapp")
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "yes", "1")
print(f"Database host: {DATABASE_HOST}")
print(f"Debug mode: {DEBUG}")
To run this with custom environment variables:
DATABASE_HOST=custom-host.example.com DEBUG=true python app.py
Output:
Database host: custom-host.example.com
Debug mode: True
Using python-dotenv
For local development, .env
files can be convenient. The python-dotenv
package loads environment variables from a file:
pip install python-dotenv
Create a .env
file:
# .env - Do not commit to source control!
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=myapp
DATABASE_USER=user
DATABASE_PASSWORD=secret
DEBUG=true
Then in your code:
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Access variables
DATABASE_HOST = os.getenv("DATABASE_HOST")
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "yes", "1")
print(f"Database host: {DATABASE_HOST}")
print(f"Debug mode: {DEBUG}")
Configuration Libraries
For more complex applications, dedicated configuration libraries provide additional features.
Using Python-Decouple
Python-decouple is a library that helps separate configuration from code:
pip install python-decouple
Create a .env
file as shown earlier, then:
from decouple import config
DATABASE_HOST = config('DATABASE_HOST', default='localhost')
DATABASE_PORT = config('DATABASE_PORT', default=5432, cast=int)
DEBUG = config('DEBUG', default=False, cast=bool)
print(f"Database host: {DATABASE_HOST}")
print(f"Database port: {DATABASE_PORT}")
print(f"Debug mode: {DEBUG}")
Using Dynaconf
Dynaconf is a more feature-rich configuration library:
pip install dynaconf
Create a settings.toml
file:
[default]
database_host = "localhost"
database_port = 5432
debug = false
[development]
debug = true
[production]
database_host = "prod-db.example.com"
Then in your code:
from dynaconf import Dynaconf
# Load settings
settings = Dynaconf(
settings_files=["settings.toml", ".secrets.toml"],
environments=True,
)
print(f"Database host: {settings.database_host}")
print(f"Debug mode: {settings.debug}")
To switch environments:
ENV_FOR_DYNACONF=production python app.py
Real-World Example: Configurable Web Application
Let's build a simple Flask application with flexible configuration:
import os
from flask import Flask, jsonify
from dynaconf import Dynaconf
# Load configuration
settings = Dynaconf(
settings_files=["settings.toml", ".secrets.toml"],
environments=True,
env=os.getenv("FLASK_ENV", "development"),
)
app = Flask(__name__)
@app.route('/')
def index():
return jsonify({
"app_name": settings.app_name,
"environment": settings.current_env,
"debug": settings.debug,
})
@app.route('/config')
def config():
# Only show non-sensitive configuration
safe_config = {
"app_name": settings.app_name,
"environment": settings.current_env,
"debug": settings.debug,
"database": {
"host": settings.database.host,
"port": settings.database.port,
"name": settings.database.name,
}
}
return jsonify(safe_config)
if __name__ == '__main__':
app.run(
debug=settings.debug,
host=settings.server.host,
port=settings.server.port
)
With a corresponding settings.toml
:
[default]
app_name = "My Configurable App"
debug = false
[default.server]
host = "0.0.0.0"
port = 8000
[default.database]
host = "localhost"
port = 5432
name = "myapp"
[development]
debug = true
[production]
debug = false
[production.server]
port = 5000
[production.database]
host = "production-db.example.com"
Best Practices for Configuration Management
- Never hardcode sensitive information in your source code
- Use environment variables for sensitive values and deployment-specific settings
- Provide sensible defaults for all configuration options
- Validate configuration at application startup
- Document all configuration options clearly
- Keep configuration simple – avoid overly complex hierarchies
- Use appropriate security measures for sensitive configuration data
- Version control configuration templates but not actual configuration files with secrets
- Consider centralized configuration for microservices and distributed applications
Configuration Security Considerations
When dealing with sensitive configuration:
- Don't store secrets in code repositories
- Use environment variables or dedicated secret management tools
- Encrypt sensitive configuration files if they must be stored
- Use dedicated services like AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault for production secrets
- Rotate secrets regularly
- Limit access to production configuration
- Audit configuration changes
Summary
Python offers many approaches to configuration management, from simple Python files to sophisticated libraries:
- Config files (Python, INI, JSON, YAML) – Simple and flexible
- Environment variables – Good for containerized applications and 12-Factor methodology
- Dedicated libraries (python-dotenv, python-decouple, dynaconf) – Added features for larger applications
- Secret management services – For secure handling of sensitive configuration
The best approach depends on your application's complexity, deployment environment, and security requirements. For simple scripts, a Python file may be sufficient. For microservices in containerized environments, environment variables or a dedicated configuration service might be more appropriate.
Additional Resources
- The Twelve-Factor App: Config
- Python configparser documentation
- Python-dotenv on GitHub
- Dynaconf documentation
- Python-decouple documentation
Exercises
- Create a simple Python application that reads configuration from a YAML file.
- Modify the application to fall back to environment variables if the configuration file is not found.
- Implement a configuration scheme that has different settings for development, testing, and production environments.
- Create a Flask application that uses Dynaconf for configuration management.
- Implement a secure way to handle database credentials in a Python application.
By mastering configuration management in Python, you'll build more flexible, secure, and maintainable applications that can be easily deployed across different environments.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)