Simplified environment variable parsing with type casting, validation, and framework integration
—
Environment configuration utilities including .env file reading, variable expansion, prefixed parsing, validation management, and configuration export capabilities.
Read .env files into the environment with automatic discovery, recursion control, and override management.
@staticmethod
def read_env(path=None, *, recurse=True, verbose=False, override=False, return_path=False):
"""
Read a .env file into os.environ.
Parameters:
- path: str or Path, specific .env file path (optional, defaults to search)
- recurse: bool, whether to search parent directories (default: True)
- verbose: bool, whether to print loading information (default: False)
- override: bool, whether to override existing environment variables (default: False)
- return_path: bool, whether to return the path of loaded file (default: False)
Returns:
bool or str: Success status or file path if return_path=True
Notes:
When path is None, searches for .env starting from caller's directory.
With recurse=True, walks up directory tree until .env is found.
"""Usage examples:
from environs import Env, env
# Load .env from current directory or parents
env.read_env()
# Load specific .env file
env.read_env("/path/to/specific/.env")
# Load with verbose output
env.read_env(verbose=True)
# Override existing environment variables
env.read_env(override=True)
# Get the path of loaded file
env_path = env.read_env(return_path=True)
print(f"Loaded environment from: {env_path}")
# Class method usage
success = Env.read_env("./config/.env")
if success:
print("Environment file loaded successfully")Parse environment variables with common prefixes using context manager for clean scoping.
def prefixed(self, prefix: str):
"""
Context manager for parsing environment variables with a common prefix.
Parameters:
- prefix: str, prefix to prepend to all variable names within context
Returns:
Iterator[Env]: Context manager yielding the current Env instance
Notes:
Prefixes can be nested. The context manager automatically manages
prefix state and restores previous prefix on exit.
"""Usage examples:
import os
from environs import env
# Set prefixed environment variables
os.environ["DB_HOST"] = "localhost"
os.environ["DB_PORT"] = "5432"
os.environ["DB_NAME"] = "myapp"
os.environ["REDIS_HOST"] = "redis.example.com"
os.environ["REDIS_PORT"] = "6379"
# Parse with prefix
with env.prefixed("DB_"):
host = env.str("HOST") # Reads DB_HOST
port = env.int("PORT") # Reads DB_PORT
name = env.str("NAME") # Reads DB_NAME
print(f"Database: {name} at {host}:{port}")
# Nested prefixes
os.environ["API_V1_ENDPOINT"] = "https://api.v1.example.com"
os.environ["API_V1_TIMEOUT"] = "30"
with env.prefixed("API_"):
with env.prefixed("V1_"):
endpoint = env.str("ENDPOINT") # Reads API_V1_ENDPOINT
timeout = env.int("TIMEOUT") # Reads API_V1_TIMEOUT
# Multiple prefix contexts
def load_db_config():
with env.prefixed("DB_"):
return {
"host": env.str("HOST", "localhost"),
"port": env.int("PORT", 5432),
"name": env.str("NAME", "app"),
"user": env.str("USER", "app"),
"password": env.str("PASSWORD")
}
def load_redis_config():
with env.prefixed("REDIS_"):
return {
"host": env.str("HOST", "localhost"),
"port": env.int("PORT", 6379),
"db": env.int("DB", 0)
}
db_config = load_db_config()
redis_config = load_redis_config()Expand environment variables using ${VAR} and ${VAR:-default} syntax for dynamic configuration.
# Variable expansion is enabled via constructor parameter
def __init__(self, *, expand_vars=False, **kwargs):
"""
Enable variable expansion in environment values.
Parameters:
- expand_vars: bool, whether to expand ${VAR} syntax (default: False)
Expansion syntax:
- ${VAR}: Expand to value of VAR, error if not set
- ${VAR:-default}: Expand to value of VAR, or 'default' if not set
- \\$: Escape $ character to prevent expansion
"""Usage examples:
import os
from environs import Env
# Set up base environment variables
os.environ["BASE_URL"] = "https://api.example.com"
os.environ["API_VERSION"] = "v1"
os.environ["USER_NAME"] = "john_doe"
# Environment variables with expansion syntax
os.environ["API_ENDPOINT"] = "${BASE_URL}/${API_VERSION}"
os.environ["USER_PROFILE_URL"] = "${BASE_URL}/${API_VERSION}/users/${USER_NAME}"
os.environ["BACKUP_PATH"] = "/backups/${USER_NAME:-anonymous}"
os.environ["LOG_FILE"] = "/var/log/${APP_NAME:-myapp}.log"
# Create Env with expansion enabled
env = Env(expand_vars=True)
# Parse expanded variables
api_endpoint = env.str("API_ENDPOINT") # => "https://api.example.com/v1"
profile_url = env.str("USER_PROFILE_URL") # => "https://api.example.com/v1/users/john_doe"
backup_path = env.str("BACKUP_PATH") # => "/backups/john_doe"
log_file = env.str("LOG_FILE") # => "/var/log/myapp.log" (uses default)
# Escaped variables
os.environ["LITERAL_DOLLAR"] = "Price: \\$19.99"
price = env.str("LITERAL_DOLLAR") # => "Price: $19.99"Manage deferred validation and prevent further parsing after configuration is complete.
def seal(self):
"""
Validate all parsed values and prevent new values from being added.
Raises:
EnvValidationError: If any parsed values failed validation
Notes:
After sealing, attempting to parse new variables raises EnvSealedError.
Useful for ensuring configuration is complete and valid before app startup.
"""Usage examples:
import os
from environs import Env, EnvValidationError, EnvSealedError
# Create non-eager env for deferred validation
env = Env(eager=False)
# Set up environment with some invalid values
os.environ["VALID_PORT"] = "8080"
os.environ["INVALID_PORT"] = "not_a_number"
os.environ["VALID_TIMEOUT"] = "30"
# Parse variables (errors collected, not raised immediately)
valid_port = env.int("VALID_PORT") # Success
invalid_port = env.int("INVALID_PORT") # Error collected
valid_timeout = env.int("VALID_TIMEOUT") # Success
# Validate all at once
try:
env.seal()
print("All configuration valid!")
except EnvValidationError as e:
print(f"Configuration errors: {e.error_messages}")
# Handle validation errors appropriately
# After sealing, no new parsing allowed
try:
new_value = env.str("NEW_VAR")
except EnvSealedError:
print("Cannot parse new variables after sealing")Export parsed configuration as a dictionary for serialization or debugging.
def dump(self):
"""
Dump parsed environment variables to a dictionary.
Returns:
dict: Dictionary of parsed values with simple data types
Notes:
Complex objects are serialized to simple types (strings, numbers).
Useful for logging configuration or creating configuration backups.
"""Usage examples:
import os
import json
from environs import env
from datetime import datetime
# Set up various environment variables
os.environ["APP_NAME"] = "MyApp"
os.environ["DEBUG"] = "true"
os.environ["PORT"] = "8080"
os.environ["CREATED_AT"] = "2023-12-25T10:30:00"
os.environ["FEATURES"] = "auth,logging,metrics"
# Parse various types
app_name = env.str("APP_NAME")
debug = env.bool("DEBUG")
port = env.int("PORT")
created_at = env.datetime("CREATED_AT")
features = env.list("FEATURES")
# Export configuration
config_dict = env.dump()
print("Current configuration:")
print(json.dumps(config_dict, indent=2, default=str))
# Save configuration to file
with open("config_backup.json", "w") as f:
json.dump(config_dict, f, indent=2, default=str)
# Use for debugging
def log_configuration():
config = env.dump()
print("Application started with configuration:")
for key, value in config.items():
# Don't log sensitive values
if "password" in key.lower() or "secret" in key.lower():
print(f" {key}: [REDACTED]")
else:
print(f" {key}: {value}")Configure environment parsing behavior through constructor parameters.
def __init__(self, *, eager=True, expand_vars=False, prefix=None):
"""
Initialize environment parser with behavior options.
Parameters:
- eager: bool, whether to raise validation errors immediately (default: True)
- expand_vars: bool, whether to expand ${VAR} syntax (default: False)
- prefix: str, global prefix for all variable names (default: None)
"""Usage examples:
from environs import Env
# Eager validation (default) - errors raised immediately
eager_env = Env(eager=True)
# Deferred validation - collect errors for later handling
deferred_env = Env(eager=False)
# Variable expansion enabled
expanding_env = Env(expand_vars=True)
# Global prefix
prefixed_env = Env(prefix="MYAPP_")
# Combined options
flexible_env = Env(
eager=False,
expand_vars=True,
prefix="APP_"
)
# Example with deferred validation
import os
os.environ["INVALID_NUMBER"] = "not_a_number"
deferred = Env(eager=False)
value = deferred.int("INVALID_NUMBER") # No error raised yet
try:
deferred.seal() # Now validation errors are raised
except EnvValidationError as e:
print(f"Validation failed: {e.error_messages}")from typing import Dict, Any, Union, Optional, Iterator
from pathlib import Path
ErrorMapping = Dict[str, List[str]]
ConfigDict = Dict[str, Any]Install with Tessl CLI
npx tessl i tessl/pypi-environs