Advanced .env loader with imports, conditionals, typing, validation, and compilation to standard format
Project description
dotenv-fusion
An advanced .env loader for Python with variable expansion, imports, conditions, typing and validation.
Compatible with other dotenv loaders: all advanced directives start with #@ and are seen as comments by standard tools.
Installation
The software is currently in preview mode; only the UVX version from GitLab is available at this time.
# run directly with uvx from Gitlab (no install needed)
uvx git+https://gitlab.com/pytgaen-group/dotenv-fusion
# Install from PyPI (when published)
pip install dotenv-fusion
# Or with uv
uv pip install dotenv-fusion
# Or run directly with uvx (no install needed)
uvx dotenv-fusion load
# Or install from source
pip install .
Usage
Shell integration (primary usage)
# Bash / Zsh
eval "$(dotenv-fusion load)"
eval "$(dotenv-fusion load -f .env.production)"
# Fish
eval (dotenv-fusion load -o fish)
# Example workflow
eval "$(dotenv-fusion load -f .env.production)"
echo $DATABASE_URL
Convenient aliases
Add to your ~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish:
# Bash/Zsh
alias dof='eval "$(dotenv-fusion load)"'
alias dofo='eval "$(dotenv-fusion load --override)"'
# Fish
alias dof='eval (dotenv-fusion load -o fish)'
alias dofo='eval (dotenv-fusion load -o fish --override)'
Then simply run dof to load your .env file, or dofo to load with override.
🚀 Meet Fuse: Universal .env Compatibility
The Problem: Tools like VS Code, Docker, and most editors only understand standard .env format. They can't parse advanced directives like #@import, #@if, etc.
The Solution: dotenv-fusion introduces a compilation system that transforms your advanced .env-fuse files into standard .env format, making them compatible with every tool while keeping all the power of advanced features.
# 1. Write advanced config with all features
# .env-fuse
#@import configs/database-${ENV}.env
#@if ${ENV} == production
LOG_LEVEL=error
#@endif
# 2. Compile to standard .env (one command!)
dotenv-fusion fuse --var ENV=production
# 3. VS Code, Docker, and all tools work perfectly!
# .env is now a flat file with resolved values
Why this is powerful
- ✨ Universal Compatibility: Your
.envfiles work with VS Code, Docker, Kubernetes, and every tool - 🎯 Keep Advanced Features: Use imports, conditions, and variables in
.env-fuse - 🏗️ Build-Time Variables: Compile different environments with
--varflags - ⚡ Zero Configuration: No VS Code extensions, no Docker plugins needed
- 🔄 Like TypeScript for .env:
.env-fuse→ compile →.env(just like.ts→.js)
Compilation Workflows
Multi-Environment Builds (CI/CD):
# Development
dotenv-fusion fuse --var ENV=dev -o .env.dev
# Staging
dotenv-fusion fuse --var ENV=staging -o .env.staging
# Production EU
dotenv-fusion fuse --var ENV=prod --var REGION=eu -o .env.prod-eu
# Production US
dotenv-fusion fuse --var ENV=prod --var REGION=us -o .env.prod-us
Docker Integration:
# Compile during build
ARG BUILD_ENV=production
RUN dotenv-fusion fuse --var ENV=${BUILD_ENV}
# Now .env is available for all other tools!
One-Command Development:
# Compile and load in one shot
dotenv-fusion fuse --load --var ENV=dev
# Variables are in your environment, .env file created
Dynamic Imports:
# .env-fuse
#@import secrets/${TENANT_ID}/api-keys.env
#@import configs/features-${FEATURE_SET}.env
# Compile for different tenants/features
dotenv-fusion fuse --var TENANT_ID=customer-123 --var FEATURE_SET=premium
dotenv-fusion fuse --var TENANT_ID=customer-456 --var FEATURE_SET=basic
Fuse Command Reference
# Basic compilation
dotenv-fusion fuse # .env-fuse → .env
# With compilation variables (build-time variables)
dotenv-fusion fuse --var ENV=prod # Single variable
dotenv-fusion fuse --var ENV=prod --var REGION=eu # Multiple variables
# Custom source/target
dotenv-fusion fuse -f .env-fuse.prod # Custom source
dotenv-fusion fuse -o .env.production # Custom target
# Compile and load
dotenv-fusion fuse --load # Compile then load
dotenv-fusion fuse --load --var ENV=prod # With variables
# Output options
dotenv-fusion fuse --stdout # Print to stdout
dotenv-fusion fuse --strip-comments # No header comments
dotenv-fusion fuse -v # Verbose mode
# Search parent directories
dotenv-fusion fuse --walk # Search 1 parent (default)
dotenv-fusion fuse --walk-up 3 # Search up to 3 parents
Auto-Fallback in Load
The load command automatically falls back to .env-fuse if .env doesn't exist:
# Smart fallback
eval "$(dotenv-fusion load)"
# 1. Looks for .env
# 2. If not found → loads .env-fuse (with compilation)
# 3. If not found → loads .env-fuse.local
# With compilation variables
eval "$(dotenv-fusion load --var ENV=prod --var REGION=eu)"
# Search parent directories (e.g., from a subdirectory)
eval "$(dotenv-fusion load --walk)" # Search 1 parent (default)
eval "$(dotenv-fusion load --walk-up 3)" # Search up to 3 parents
DOTENV_FUSION_WALK_DEPTH=5 dotenv-fusion load --walk # Custom default depth
File Naming Convention
Like .env files, .env-fuse follows the same patterns:
.env-fuse- Base configuration (source file).env-fuse.local- Local developer overrides (gitignored).env-fuse.production- Production config.env-fuse.development- Development config
Compiled files remove the -fuse suffix:
.env-fuse→.env.env-fuse.production→.env.production
CLI commands
# Load and export variables for shell integration
dotenv-fusion load [options]
# List all variable names
dotenv-fusion list [-f FILE]
# Show variable documentation
dotenv-fusion docs [-f FILE]
# Generate template .env file
dotenv-fusion template [-f FILE] > .env.example
# Load command options:
-f, --file FILE File to load (default: .env)
-o, --format FORMAT Output format: bash, zsh, fish, json (default: bash)
--override Override existing environment variables
--var, -V KEY=VALUE Compilation variable (e.g., --var ENV=prod)
--walk Search parent directories if not found locally (default: 1 level, configure with DOTENV_FUSION_WALK_DEPTH)
--walk-up N Search up to N parent directories (implies --walk)
-v, --verbose Verbose mode: -v (logic and names), -vv (values with secrets masked)
-d, --debug Show resolved values
-t, --typed Show values with types
-h, --help Help
Examples:
dotenv-fusion docs # Show variable documentation
dotenv-fusion template > .env.example # Generate template
dotenv-fusion load -f .env.local -d # Debug mode
dotenv-fusion load -o json # Output as JSON
dotenv-fusion load -v # Verbose: show logic and variable names
dotenv-fusion load -vv # Very verbose: show values (secrets masked)
Python API (optional)
For Python projects that need programmatic access:
from dotenv_fusion import load_dotenv, get_env
# Load .env file
values = load_dotenv() # or load_dotenv(".env.production")
# Get a variable
port = get_env("PORT", "3000")
# Variables are also available via os.environ
import os
print(os.environ["APP_NAME"])
Single-script usage
The core dotenvfusion.py can be used standalone without installation:
# Direct usage
python -m dotenv_fusion.dotenvfusion -f .env --debug
# Or copy the file for standalone usage
cp src/dotenv_fusion/dotenvfusion.py /usr/local/bin/
python /usr/local/bin/dotenvfusion.py --help
Features
Variable expansion
# Internal variables
BASE_URL=http://localhost
API_URL=${BASE_URL}/api/v1
# System variables
DATA_DIR=$HOME/data
WORK_DIR=$PWD/workspace
# Default value
CACHE_DIR=${CACHE_PATH:-/tmp/cache}
TIMEOUT=${REQUEST_TIMEOUT:-30}
Quotes
# Double quotes: variable expansion + escape sequences
MESSAGE="Welcome to ${APP_NAME}\nVersion: ${VERSION}"
# Single quotes: literal value (no expansion)
PATTERN='${NOT_EXPANDED}'
# No quotes: expansion + inline comments
DEBUG=true # This is a comment
Escaping
# Literal dollar
PRICE="\$99.99"
REGEX='\$[0-9]+'
# Backslash
PATH="C:\\Users\\name"
Directives
All directives start with #@ to remain compatible with other loaders.
File imports
# Simple import (error if file doesn't exist)
#@import database.env
# With prefix (HOST becomes DB_HOST)
#@import database.env prefix=DB_
# Optional import (ignored if file doesn't exist)
#@import local.env mode=ifexist
# Override (overwrites existing variables)
#@import overrides.env override=true
# Combined
#@import secrets.env prefix=SECRET_ mode=ifexist override=true
# Dynamic path
#@import configs/${ENV}.env
Options:
prefix=PREFIX_: prefix added to imported variablesoverride=true: overwrite already defined variables (default: first-wins)mode=ifexist: silently ignore if file doesn't exist
Variable definitions
The #@def directive defines type, validation, required status, default value and documentation for a variable:
#@def PORT type=int default=3000 validate=^\d+$ doc="HTTP listening port"
#@def DEBUG type=bool default=false doc="Enable debug mode"
#@def API_KEY required=true doc="API key (required)"
#@def EMAIL validate=^[\w.-]+@[\w.-]+\.\w+$ doc="Contact email"
#@def TAGS type=list default=web,api doc="Comma-separated tags"
#@def CONFIG type=json doc="JSON configuration"
PORT=8080
API_KEY=secret-key
EMAIL=contact@example.com
Options:
type=: int, float, bool, str, list, json (default: str)required=true: error if variable is not defined (default: false)default=: default value if not definedvalidate=: regex pattern to validate the valuesecret=true: mark variable as secret (masked in verbose mode)lazy=true: preserve${VAR}references in fuse output instead of resolving (default: false)doc=: variable description
Show documentation:
dotenv-fusion docs
# PORT (int) (default: 3000): HTTP listening port
# DEBUG (bool) (default: false): Enable debug mode
# API_KEY (str) [required]: API key (required)
Verbose mode and secrets
Verbose mode outputs diagnostic information to stderr while loading:
# Level 1: Show logic and variable names
dotenv-fusion load -v
# Output (stderr):
# [LOAD] .env
# [VAR] APP_NAME
# [VAR] DATABASE_PASSWORD (secret)
# [VAR] API_KEY (secret)
# [SUCCESS] Loaded 3 variables from 1 file(s)
# Level 2: Show values with secrets masked
dotenv-fusion load -vv
# Output (stderr):
# [LOAD] .env
# [VAR] APP_NAME = MyApp
# [VAR] DATABASE_PASSWORD = ***REDACTED*** (secret)
# [VAR] API_KEY = ***REDACTED*** (secret)
# [SUCCESS] Loaded 3 variables from 1 file(s)
Secret detection: Variables are automatically marked as secrets if their name contains common patterns (password, secret, token, key, api_key, credential, etc.). You can also explicitly mark variables as secrets:
#@def MY_TOKEN type=str secret=true doc="Explicitly marked as secret"
MY_TOKEN=my-secret-value
#@def PUBLIC_VAR type=str doc="Not a secret"
PUBLIC_VAR=public-value
Note: Secrets are only masked in verbose output (stderr). The actual export commands and environment variables contain the real values for proper shell integration.
# Verbose output goes to stderr (visible but doesn't affect eval)
eval "$(dotenv-fusion load -vv)"
# Environment variables contain real values
echo $API_KEY # Shows the actual secret value, not ***REDACTED***
Generate template:
dotenv-fusion template > .env.example
Conditions
ENV=production
#@if ${ENV} == production
LOG_LEVEL=error
DEBUG=false
#@else
LOG_LEVEL=debug
DEBUG=true
#@endif
# Existence test
#@ifdef CI
CI_MODE=true
#@endif
#@ifndef LOCAL
REMOTE=true
#@endif
Supported operators: ==, !=, >, <, >=, <=
Processing modes
The #@mode directive changes how subsequent lines are processed:
# Enable override mode: later definitions override earlier ones
#@mode override=true
API_URL=https://prod.api.com # This will override any previous API_URL
# Enable strict mode: error if variable is undefined
#@mode strict=true
VALUE=${MUST_EXIST} # Error if MUST_EXIST doesn't exist
# Combine multiple modes on one line
#@mode override=true strict=true
# Disable modes
#@mode override=false strict=false
Available modes:
override=true/false- When true, variable definitions override existing values (default: no, first-wins)strict=true/false- When true, undefined variables raise an error instead of becoming empty (default: no)
By default, an undefined variable becomes an empty string and first definition wins.
Complete examples
Web application
# .env
#@def APP_NAME required=true doc="Application name"
#@def ENV required=true validate=^(dev|staging|production)$ doc="Environment"
#@def PORT type=int default=3000 doc="HTTP port"
#@def DEBUG type=bool default=false doc="Debug mode"
APP_NAME=MyApp
ENV=production
#@import configs/base.env
#@import configs/${ENV}.env
#@import .env.local mode=ifexist
#@if ${ENV} == production
#@import secrets/prod.env prefix=SECRET_
#@else
#@import secrets/dev.env prefix=SECRET_
#@endif
Microservices
# .env
#@import services/database.env prefix=DB_
#@import services/redis.env prefix=REDIS_
#@import services/rabbitmq.env prefix=MQ_
# Result:
# DB_HOST, DB_PORT, DB_USER, DB_PASS
# REDIS_HOST, REDIS_PORT
# MQ_HOST, MQ_PORT, MQ_VHOST
CI/CD
# .env
#@ifdef CI
#@import ci/test.env override=true
#@endif
#@ifdef DOCKER
#@import docker/container.env
#@endif
#@if ${DEPLOY_TARGET} == aws
#@import deploy/aws.env
#@endif
#@if ${DEPLOY_TARGET} == gcp
#@import deploy/gcp.env
#@endif
Behavior
Resolution order
- Compilation variables (
--var) - highest priority - Variables defined in the current file and imported files (in order)
- System environment variables
- Default values (
${VAR:-default})
Precedence (first-wins)
By default, the first definition wins:
# .env
PORT=3000
#@import other.env # If other.env contains PORT=8080, it's ignored
Use override=true to change this behavior.
Circular imports
Circular imports are detected and silently ignored.
Security
Shell escaping
When using eval "$(dotenv-fusion load)", all values are properly escaped to prevent command injection:
shlex.quote(): Uses Python's standard shell escaping function- Variable name validation: Only alphanumeric + underscore names are exported
- Command injection prevention: Dangerous values like
$(rm -rf /)are safely escaped
# Example: dangerous value is safely escaped
DANGEROUS=$(echo "pwned") # Will be exported as '$(echo "pwned")' - NOT executed
# Safe to use with eval
eval "$(dotenv-fusion load)"
echo $DANGEROUS # Shows: $(echo "pwned") - the literal string
Best practices
- Always use
eval "$(dotenv-fusion load)"pattern for shell integration - Review
.envfiles before loading untrusted sources - Use
--debugflag to inspect values before loading:dotenv-fusion load --debug
Python API
from dotenv_fusion import DotenvLoader, load_dotenv, get_env
# Simple function
values = load_dotenv(".env", override=False, apply_types=True)
# Class for more control
loader = DotenvLoader()
values = loader.load(".env")
# Access raw values (strings)
raw_values = loader.values
# Access typed values
typed_values = loader.typed_values
# Get documentation
docs = loader.get_docs()
# Generate template
template = loader.generate_template()
License
LGPL-3.0-or-later
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dotenv_fusion-0.3.0.tar.gz.
File metadata
- Download URL: dotenv_fusion-0.3.0.tar.gz
- Upload date:
- Size: 19.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b73576985ce52561eedeb25736d21a0d00e55833d1289dac60a73d4020f2c9e
|
|
| MD5 |
4e10b075da7fd43f32ec3a5f0545ac7a
|
|
| BLAKE2b-256 |
bbe551f74c8b85ba3ddf7c165f23eb32cdee1d584b986a04fcade7e040ebb04b
|
File details
Details for the file dotenv_fusion-0.3.0-py3-none-any.whl.
File metadata
- Download URL: dotenv_fusion-0.3.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b445f83540d19983ce2d8b687d779dd766357a41324a5fbebd778e538ec19284
|
|
| MD5 |
aa5fe382bee1273049ebcc5b16d68cb4
|
|
| BLAKE2b-256 |
bf1040d959012014cb3edbf2ddcc944c7820196130146b08ce2ad02af72144ab
|