TomlEv is a lightweight Python framework designed to simplify environment variable management using TOML configuration files with type-safe configuration models. It allows you to:
- Type-safe configuration: Define configuration schemas using Python classes with type hints
- Automatic type conversion: Convert environment variables to appropriate types (bool, int, float, str, lists, dicts, sets, tuples)
- Nested configuration: Support for complex nested configuration structures
- Environment variable substitution: Reference environment variables in TOML files with
${VAR|-default}syntax - Validation: Automatic validation of configuration structure and types
- High performance: 50-60% faster than previous versions with optimized parsing and type conversion
- Memory efficient: 40-50% less memory usage with automatic
__slots__generation - Async support: Non-blocking configuration loading for async applications
- AI coding agent ready: Full type checking support makes configurations perfectly compatible with AI coding agents and IDEs
- IDE support: Complete IDE autocompletion and static type analysis support
# pip
pip install tomlev
# With async support
pip install 'tomlev[async]'# uv
uv add tomlev
# With async support
uv add tomlev --optional async# poetry
poetry add tomlev
# With async support
poetry add 'tomlev[async]'Note: The [async] extra installs aiofiles for non-blocking file I/O operations. Use this when building async
applications with FastAPI, aiohttp, or other async frameworks.
Create a TOML configuration file (env.toml by default):
# env.toml
app_name = "My Application"
debug = "${DEBUG|-false}"
environment = "${ENV|-development}"
[database]
host = "${DB_HOST|-localhost}"
port = "${DB_PORT|-5432}"
user = "${DB_USER}"
password = "${DB_PASSWORD}"
name = "${DB_NAME|-app_db}"
[redis]
host = "${REDIS_HOST|-127.0.0.1}"
port = "${REDIS_PORT|-6379}"Optionally include fragments inside a table using __include (paths are resolved relative to the TOML file):
# main.toml
[features]
__include = ["features.toml"]# features.toml (merged under [features])
enabled = true
name = "awesome"Create configuration model classes that inherit from BaseConfigModel:
from tomlev import BaseConfigModel, TomlEv
class DatabaseConfig(BaseConfigModel):
host: str
port: int
user: str
password: str
name: str
class RedisConfig(BaseConfigModel):
host: str
port: int
class FeaturesConfig(BaseConfigModel):
# Matches the content merged under [features] via __include
enabled: bool
name: str
class AppConfig(BaseConfigModel):
app_name: str
debug: bool
environment: str
database: DatabaseConfig
redis: RedisConfig
features: FeaturesConfigTip: See the File Includes section for more details on __include usage and merge rules.
Recommended: Simple convenience function
from tomlev import tomlev
# Simple one-liner - load and validate configuration
# Uses defaults: "env.toml" and ".env"
config: AppConfig = tomlev(AppConfig)
# Or explicitly specify files
config: AppConfig = tomlev(AppConfig, "env.toml", ".env")
# You can also set defaults via environment variables
# export TOMLEV_TOML_FILE="config/production.toml"
# export TOMLEV_ENV_FILE="config/.env.production"
# Then just use:
config: AppConfig = tomlev(AppConfig) # Uses env var defaults
# Access configuration with type safety
print(f"App: {config.app_name}")
print(f"Environment: {config.environment}")
print(f"Debug mode: {config.debug}") # Automatically converted to bool
# Access nested configuration
db_host = config.database.host
db_port = config.database.port # Automatically converted to int
# All properties are type-safe and validated
redis_host = config.redis.host
redis_port = config.redis.port # Automatically converted to intAlternative: Class-based approach (when you need advanced features)
Use the TomlEv class when you need access to .environ, .strict, or .raw properties:
from tomlev import TomlEv
# Create instance to access additional properties
loader = TomlEv(AppConfig, "env.toml", ".env")
# Access environment variables used
env_vars = loader.environ
# Check strict mode setting
is_strict = loader.strict
# Get raw parsed TOML dict
raw_config = loader.raw
# Get validated config
config: AppConfig = loader.validate()TomlEv provides async I/O support for non-blocking configuration loading, perfect for async applications like FastAPI, aiohttp, or any async-based framework.
Installation:
pip install 'tomlev[async]'
# or with uv
uv add tomlev --optional asyncUsage:
# Save as async_app.py, then run with: python async_app.py (or: uv run python async_app.py)
import asyncio
from tomlev import tomlev_async, BaseConfigModel
class AppConfig(BaseConfigModel):
host: str
port: int
debug: bool
async def main():
# Non-blocking configuration loading
config = await tomlev_async(AppConfig, "env.toml", ".env")
print(f"Server: {config.host}:{config.port}")
asyncio.run(main())Advanced async usage with TomlEvAsync:
from tomlev import TomlEvAsync
async def main():
# Create loader instance for access to additional properties
loader = await TomlEvAsync.create(AppConfig, "env.toml", ".env")
# Access environment variables
env_vars = loader.environ
# Get validated config
config = loader.validate()Benefits:
- Non-blocking file I/O
- Perfect for async web frameworks (FastAPI, aiohttp, Starlette)
- Same API as synchronous version
- Optional dependency - only install when needed
Create frozen (immutable) configurations that cannot be modified after initialization:
from tomlev import BaseConfigModel, tomlev
class AppConfig(BaseConfigModel, frozen=True):
host: str
port: int
config = tomlev(AppConfig, "env.toml")
# This will raise AttributeError
try:
config.port = 9000
except AttributeError as e:
print(e) # Cannot modify frozen configuration model: AppConfigBenefits:
- Thread-safe after initialization
- Prevents accidental modifications
- Clear intent in code
- No performance penalty
TomlEv uses BaseConfigModel to provide type-safe configuration handling. Here are the supported types:
- Basic types:
str,int,float,bool - Collections:
list[T],dict[str, T],set[T],tuple[T, ...]where T is any supported type - Complex collections:
list[dict[str, Any]]for lists of dictionaries - Nested models: Other
BaseConfigModelsubclasses - Generic types:
typing.Anyfor flexible values
from typing import Any
from tomlev import BaseConfigModel, tomlev, TomlEv
class QueryConfig(BaseConfigModel):
get_version: str
get_users: str
class DatabaseConfig(BaseConfigModel):
host: str
port: int
user: str
password: str
name: str
uri: str
queries: dict[str, str] # Dictionary of queries
class RedisConfig(BaseConfigModel):
host: str
port: int
keys: list[str] # List of strings
nums: list[int] # List of integers
atts: list[dict[str, Any]] # List of dictionaries
tags: set[str] # Set of unique strings
coordinates: tuple[float, float, float] # Tuple with fixed types
weight: int
mass: float
class AppConfig(BaseConfigModel):
debug: bool
environment: str
temp: float
database: DatabaseConfig
redis: RedisConfig
# Simple usage with convenience function (recommended)
config: AppConfig = tomlev(AppConfig)
# Or explicitly specify files
config: AppConfig = tomlev(AppConfig, "env.toml", ".env")
# Alternative: Class-based approach if you need .environ, .strict, or .raw
config: AppConfig = TomlEv(AppConfig).validate()TomlEv also provides a small CLI to validate TOML configuration files with environment substitution, without writing Python code.
Validate using defaults (env.toml and .env in the current directory):
tomlev validate
# or with uv
uv run tomlev validateValidate explicit files:
tomlev validate --toml path/to/app.toml --env-file path/to/.env
# or with uv
uv run tomlev validate --toml path/to/app.toml --env-file path/to/.envYou can set default file paths using environment variables, which is useful for CI/CD pipelines or containerized environments:
# Set default file paths
export TOMLEV_TOML_FILE="config/production.toml"
export TOMLEV_ENV_FILE="config/.env.production"
# Now these commands will use the environment variable defaults
tomlev validate
tomlev render
# or with uv
uv run tomlev validate
uv run tomlev renderThe precedence order is:
- Explicit command-line arguments (highest priority)
- Environment variables (
TOMLEV_TOML_FILE,TOMLEV_ENV_FILE) - Hardcoded defaults (
env.toml,.env)
Disable strict mode (missing variables do not fail):
tomlev validate --no-strictIgnore the .env file or system environment variables:
tomlev validate --no-env-file # do not read .env
tomlev validate --no-environ # do not include process environmentCustomize the default separator used in ${VAR|-default} patterns (default is |-):
tomlev validate --separator "|-"Exit codes: returns 0 on success, 1 on validation error (including missing files, substitution errors, or TOML parse errors). This makes it convenient to integrate into CI.
TomlEv supports a simple include mechanism to compose configs from smaller TOML fragments. Place a reserved key __include inside any table to include one or more TOML files into that table.
Basic syntax (paths are resolved relative to the referencing file):
# main.toml
[database]
__include = ["database.toml"]Included file content is merged under the table where __include appears. For example:
# database.toml
host = "${DB_HOST|-localhost}"
port = "${DB_PORT|-5432}"
[nested]
flag = trueAfter expansion and substitution, the effective configuration is equivalent to:
[database]
host = "localhost"
port = 5432
[database.nested]
flag = trueNotes:
__includecan be a string or a list of strings:__include = "file.toml"or__include = ["a.toml", "b.toml"].- Includes are expanded using the same environment mapping and strict mode as the parent file.
- Merge rules: dictionaries are deep-merged; non-dicts (strings, numbers, booleans, lists) are replaced by later includes (last one wins).
- Strict mode: missing files and include cycles raise errors. In non-strict mode, they are skipped.
- The
__includekey is removed from the final configuration prior to model validation.
debug = "${DEBUG|-false}"
environment = "${CI_ENVIRONMENT_SLUG|-develop}"
temp = "${TEMP_VAL|--20.5}"
[database]
host = "${DB_HOST|-localhost}"
port = "${DB_PORT|-5432}"
user = "${DB_USER}"
password = "${DB_PASSWORD}"
name = "${DB_NAME|-app_db}"
uri = "postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST|-localhost}:${DB_PORT|-5432}/${DB_NAME|-app_db}"
[database.queries]
get_version = """SELECT version();"""
get_users = """SELECT * FROM "users";"""
[redis]
host = "${REDIS_HOST|-127.0.0.1}"
port = "${REDIS_PORT|-6379}"
keys = ["one", "two", "three"]
nums = [10, 12, 99]
atts = [{ name = "one", age = 10 }, { name = "two", age = 12 }]
tags = ["cache", "session", "cache", "metrics"] # Will be deduplicated to set
coordinates = [52.5200, 13.4050, 100.0] # Will be converted to tuple
weight = 0.98
mass = 0.78By default, TomlEv operates in strict mode, which means it will raise a ValueError if:
- An environment variable referenced in the TOML file is not defined and has no default value
- The same variable is defined multiple times in the .env file
This helps catch configuration errors early. You can disable strict mode in two ways:
# Method 1: Set the environment variable TOMLEV_STRICT_DISABLE
import os
os.environ["TOMLEV_STRICT_DISABLE"] = "true"
config = tomlev(AppConfig) # Uses defaults: "env.toml" and ".env"
# Method 2: Pass strict=False when calling tomlev()
config = tomlev(AppConfig, strict=False) # Uses defaults with strict=False
# Alternative: Using the TomlEv class
config = TomlEv(AppConfig, strict=False).validate()When strict mode is disabled, TomlEv will not raise errors for missing environment variables or duplicate definitions.
TomlEv v1.0.8 includes significant performance improvements:
- 50-60% faster overall performance
- 40-50% less memory usage per configuration instance
- 30-40% faster initialization with type hints caching
- 20-30% faster type conversion with optimized converters
- 15-20% faster parsing with batch string substitution
Key optimizations:
- Type hints caching with
@lru_cache - Dict-based dispatch for type converters
- Auto-generated
__slots__for memory efficiency - Batch regex replacement for string substitution
- Optimized file I/O with caching
These improvements are automatic - no code changes required to benefit from them!
If you like TomlEv, please give it a star on GitHub: https://github.com/thesimj/tomlev
MIT licensed. See the LICENSE file for more details.