CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-environs

Simplified environment variable parsing with type casting, validation, and framework integration

Pending
Overview
Eval results
Files

configuration.mddocs/

Configuration Management

Environment configuration utilities including .env file reading, variable expansion, prefixed parsing, validation management, and configuration export capabilities.

Capabilities

Environment File Loading

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")

Prefixed Environment Variables

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()

Variable Expansion

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"

Validation and Sealing

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")

Configuration Export

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}")

Initialization Options

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}")

Types

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

docs

advanced-data.md

configuration.md

core-parsing.md

custom-parsers.md

file-secrets.md

framework-integration.md

index.md

specialized-types.md

validation.md

tile.json