Metadata-Version: 2.4
Name: dotenv-fusion
Version: 0.5.0
Summary: Advanced .env loader with imports, conditionals, typing, validation, and compilation to standard format
Keywords: dotenv,environment-variables,configuration,env-loader,variable-expansion,imports,conditionals,typing,validation,shell-integration,fuse,compilation
Author: Gaëtan Montury
Author-email: Gaëtan Montury <10528250-pytgaen@users.noreply.gitlab.com>
License-Expression: LGPL-3.0-or-later
License-File: LICENSE.txt
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Classifier: Typing :: Typed
Requires-Dist: pytest>=9 ; extra == 'dev'
Requires-Dist: pytest-cov>=5 ; extra == 'dev'
Requires-Dist: vermin>=1.8.0 ; extra == 'dev'
Maintainer: Gaëtan Montury
Maintainer-email: Gaëtan Montury <10528250-pytgaen@users.noreply.gitlab.com>
Requires-Python: >=3.10
Project-URL: Homepage, https://gitlab.com/pytgaen-group/dotenv-fusion
Project-URL: Documentation, https://dotenv-fusion-988a9a.gitlab.io
Project-URL: Repository, https://gitlab.com/pytgaen-group/dotenv-fusion
Project-URL: Issues, https://gitlab.com/pytgaen-group/dotenv-fusion/-/issues
Project-URL: Changelog, https://gitlab.com/pytgaen-group/dotenv-fusion/-/blob/main/CHANGELOG.md
Provides-Extra: dev
Description-Content-Type: text/markdown

<div align="center">
  <img src="https://dotenv-fusion-988a9a.gitlab.io/assets/dotenv-fusion_logo.jpg" alt="dotenv-fusion logo" width="400"/>
</div>

# dotenv-fusion

An advanced `.env` loader for Python with variable expansion, imports, conditions, typing and validation.

Advanced directives start with `#@` and remain comments for dotenv loaders that
accept comment lines. Use `fuse` to flatten directives, selecting the dialect
required by the consuming tool.

## Installation

The package is available from PyPI and can also be run directly from GitLab during development.

```bash
# Install from PyPI
pip install dotenv-fusion

# Or add it to an uv project
uv add dotenv-fusion

# Or install the CLI in an isolated uv tool environment
uv tool install dotenv-fusion

# Or run directly with uvx (no install needed)
uvx dotenv-fusion load

# Or run directly from GitLab
uvx git+https://gitlab.com/pytgaen-group/dotenv-fusion

# Or install from source
pip install .
```

## Usage

### Shell integration (primary usage)

```bash
# Bash / Zsh
eval "$(dotenv-fusion load)"
eval "$(dotenv-fusion load -f .env.production)"

# Fish
eval (dotenv-fusion load -o fish)

# PowerShell
dotenv-fusion load -o powershell | Invoke-Expression

# Validate without exporting
dotenv-fusion check
dotenv-fusion check -f .env-fuse --var ENV=prod

# Run an application without printing shell exports
dotenv-fusion exec -- application --serve

# Example workflow
eval "$(dotenv-fusion load -f .env.production)"
echo $DATABASE_URL
```

### Age-backed secrets

Keep several secrets in one age-encrypted dotenv bundle and select only the
keys used by the application:

```dotenv
# .env-fuse
#@def DB_USERNAME source="age://store/database.env.age#USERNAME"
#@def DB_PASSWORD required=true source="age://store/database.env.age#PASSWORD"
DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@database/app
```

```bash
export DOTENV_FUSION_AGE_STORE=/home/user/.local/share/dotenv-fusion/secrets

# Optional for recipient-encrypted files; repeat --age-identity for several identities
export DOTENV_FUSION_AGE_IDENTITY=/home/user/.config/age/keys.txt

dotenv-fusion check
dotenv-fusion check --no-resolve-sources  # Static validation without age access
dotenv-fusion exec -- application --serve
eval "$(dotenv-fusion load)"              # Explicit plaintext shell exports
```

The plaintext of `database.env.age` is a normal dotenv bundle such as
`USERNAME=application` and `PASSWORD=...`. It is decrypted once per invocation,
even when several selectors use it. `load` and `exec` override existing
environment variables by default. Use `--no-override` to preserve them and
avoid contacting age for values already present.

`list`, `docs`, and `template` inspect source declarations without contacting
age. `check` resolves sources by default; use `check --no-resolve-sources` for
syntax and dependency validation on runners without store access. `load`
resolves sources and emits real shell exports, so `exec` is preferred whenever
the application can be launched directly.

`fuse` refuses sourced configurations unless durable plaintext materialization
is explicitly requested:

```bash
dotenv-fusion fuse --materialize-secrets -o .env.production
dotenv-fusion fuse --materialize-secrets -o .env.production --diff
```

`--materialize-secrets` cannot be combined with `--stdout` or `--load`. New
output files use mode `0600` on POSIX; materialized diffs redact every assignment
value because an old artifact's secret provenance may no longer be available.

#### Migrate an existing `.env`

Select secrets by exact name or case-sensitive glob. Quote globs so the shell
does not expand them:

```bash
dotenv-fusion migrate \
  --from .env \
  --to .env-fuse \
  --secret '*_TOKEN' \
  --secret '*_PWD' \
  --public 'PUBLIC_*' \
  --age-output secrets/application.env.age \
  --dry-run

dotenv-fusion migrate \
  --from .env \
  --to .env-fuse \
  --secret '*_TOKEN' \
  --secret '*_PWD' \
  --public 'PUBLIC_*' \
  --age-output secrets/application.env.age \
  --age-recipient 'age1...'
```

The original `.env` is retained. Secret values are passed directly to `age`
through stdin, without a plaintext temporary file, and neither values nor age
output are printed. New outputs use mode `0600` on POSIX; existing outputs are
refused unless `--force` is explicit.

### Convenient aliases

Add to your `~/.bashrc`, `~/.zshrc`, `~/.config/fish/config.fish`, or PowerShell `$PROFILE`:

```bash
# Bash/Zsh
alias dof='eval "$(dotenv-fusion load)"'
alias dofn='eval "$(dotenv-fusion load --no-override)"'

# Fish
alias dof='eval (dotenv-fusion load -o fish)'
alias dofn='eval (dotenv-fusion load -o fish --no-override)'

# PowerShell
function dof { dotenv-fusion load -o powershell | Invoke-Expression }
function dofn { dotenv-fusion load -o powershell --no-override | Invoke-Expression }
```

Then run `dof` to load with override (the default), or `dofn` to preserve
variables that already exist in the shell.

## 🚀 Meet Fuse: Portable .env Compilation

**The Problem**: Tools like VS Code, Docker Compose, and most editors only
understand plain `.env` assignments. They can't parse advanced directives like
`#@import` or `#@if`.

**The Solution**: dotenv-fusion compiles advanced `.env-fuse` files into flat
assignments. The default `dotenv` dialect keeps the existing quoted output for
dotenv-aware consumers. Use `--dialect docker` for `docker run --env-file`,
whose parser preserves quotes instead of removing them.

```bash
# 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. The selected consumer reads a flat file with resolved values
```

### Why this is powerful

- ✨ **Explicit compatibility**: Use the default dialect for dotenv-aware tools
    and `--dialect docker` for `docker run --env-file`
- 🎯 **Keep Advanced Features**: Use imports, conditions, and variables in `.env-fuse`
- 🏗️ **Build-Time Variables**: Compile different environments with `--var` flags
- ⚡ **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):

```bash
# 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 CLI Integration**:

```bash
dotenv-fusion fuse --var ENV=production --dialect docker -o .env.docker
docker run --env-file .env.docker my-image
```

The Docker dialect writes raw values because `docker run --env-file` performs
no quote removal or escape processing. It refuses values containing newlines or
NUL bytes, which that line-oriented format cannot represent.

**One-Command Development**:

```bash
# Compile, write .env, and emit shell exports for eval
eval "$(dotenv-fusion fuse --load --var ENV=dev)"
# Variables are in your shell, .env file created
```

**Dynamic Imports**:

```bash
# .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

```bash
# 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
dotenv-fusion fuse --dialect docker -o .env.docker  # docker run --env-file

# Compile and emit shell exports
eval "$(dotenv-fusion fuse --load)"             # Compile then export via eval
eval "$(dotenv-fusion fuse --load --var ENV=prod)"  # With variables

# Output and review options
dotenv-fusion fuse --stdout                     # Print to stdout
dotenv-fusion fuse --dry-run                    # Preview without writing
dotenv-fusion fuse --diff                       # Show redacted diff; exits 1 on drift
dotenv-fusion fuse --materialize-secrets -o .env.production  # Explicit secret artifact
dotenv-fusion fuse --strip-comments             # No header comments
dotenv-fusion fuse --restrict-imports            # Confine imports to the source directory
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
```

### Automatic Input Selection in Load

The `load` command selects the first existing input in its load order:

```bash
# Default load order
eval "$(dotenv-fusion load)"
# 1. Looks for .env-fuse
# 2. If not found → loads .env-fuse.local
# 3. If not found → loads .env

# Promote .env while retaining the other fallbacks
eval "$(dotenv-fusion load --load-order=env)"
eval "$(DOTENV_FUSION_LOAD_ORDER=env dotenv-fusion load)"

# 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
```

`--load-order` and `DOTENV_FUSION_LOAD_ORDER` accept a partial comma-separated
list of `fuse`, `fuse-local`, and `env`. Listed inputs are promoted; omitted
inputs retain their default relative order. For example, `env` expands to
`env,fuse,fuse-local`. An explicit `-f/--file` bypasses automatic ordering.

`load` overrides existing environment variables by default. Use
`--no-override`, or set `DOTENV_FUSION_EXISTING_ENV=no-override`, to preserve
them. Explicit `--override` or `--no-override` takes precedence over the
environment setting.

### 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

```bash
# Resolve and export variables for shell integration
dotenv-fusion load [options]

# Validate without exporting or writing output
dotenv-fusion check [options]

# Resolve sources and run a command directly
dotenv-fusion exec [options] -- COMMAND [ARG...]

# Migrate selected variables from .env into an age bundle
dotenv-fusion migrate [options]

# List all variable names
dotenv-fusion list [options]

# Show variable documentation
dotenv-fusion docs [options]

# Generate template .env file
dotenv-fusion template [options] > .env.example

# Compile .env-fuse to standard .env
dotenv-fusion fuse [options]

# Load command options:
  -f, --file FILE      File to load (default: auto-detect by load order)
  -o, --format FORMAT  Output format: bash, zsh, fish, powershell, json (default: bash)
  --load-order ORDER   Promote auto-detected inputs: fuse, fuse-local, env
  --override           Override existing environment variables (default)
  --no-override        Preserve existing environment variables
  --var, -V KEY=VALUE  Compilation variable (e.g., --var ENV=prod)
  --restrict-imports   Confine imports to the source file's parent directory
  --age-identity PATH  Age identity file (repeatable)
  --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 with secrets masked
  -t, --typed          Show values with types and secrets masked
  -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:

```python
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 for
regular dotenv features:

```bash
# Direct usage
python src/dotenv_fusion/dotenvfusion.py load -f .env --debug

# Or install the standalone core with curl
DOTENV_FUSION_REF=main  # Use a release tag for a reproducible installation
install -d ~/.local/bin
curl -fsSLo ~/.local/bin/dotenv-fusion \
  "https://gitlab.com/pytgaen-group/dotenv-fusion/-/raw/${DOTENV_FUSION_REF}/src/dotenv_fusion/dotenvfusion.py"
chmod +x ~/.local/bin/dotenv-fusion
```

pip, uv, and uvx installs include the official age backend. For a standalone
installation, add it as an optional sidecar:

```bash
DOTENV_FUSION_REF=main  # Use the same ref as the standalone core
install -d ~/.local/lib/dotenv-fusion/backends
curl -fsSLo ~/.local/lib/dotenv-fusion/backends/age.py \
  "https://gitlab.com/pytgaen-group/dotenv-fusion/-/raw/${DOTENV_FUSION_REF}/src/dotenv_fusion/backends/age.py"
```

An executable installed as `<prefix>/bin/dotenv-fusion` discovers sidecars in
`<prefix>/lib/dotenv-fusion/backends/`. An absolute
`DOTENV_FUSION_BACKEND_PATH` overrides that location. URI schemes remain
allowlisted by the core; `.env-fuse` cannot select an arbitrary Python module.
Always download the standalone core and its sidecars from the same ref. For a
stable installation, replace `main` with a release tag.

## Features

### Variable expansion

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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 variables
- `override=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:

```bash
#@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 defined
- `validate=`: regex pattern to validate the value
- `secret=true/false`: explicitly mark or unmark a secret; when omitted, the
    variable name is used for auto-detection
- `lazy=true`: preserve `${VAR}` references in fuse output instead of resolving (default: false)
- `doc=`: variable description

Show documentation:

```bash
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:

```bash
# 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)
```

With `--no-override -v`, shell export commands also report variables skipped
because the parent environment wins, followed by an `[EXPORT]` count. These
diagnostics go to stderr and never alter the stdout stream consumed by `eval`.

**Secret detection**: Variables are automatically marked as secrets if their name contains common patterns (password, secret, token, key, api_key, credential, etc.). Use `secret=true` to force masking or `secret=false` to override a false positive:

```bash
#@def MY_TOKEN type=str secret=true doc="Explicitly marked as secret"
MY_TOKEN=my-secret-value

#@def PUBLIC_KEY type=str secret=false doc="Explicitly public despite its name"
PUBLIC_KEY=public-value
```

**Note**: Secrets and values derived from them are masked in verbose, `--debug`,
and `--typed` diagnostic output. The actual export commands and environment
variables contain the real values for proper shell integration. Diagnostic
commands do not provide an option to reveal secrets.

```bash
# 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:

```bash
dotenv-fusion template > .env.example
```

Secret values and values derived from them are emitted as empty placeholders;
public values and defaults remain available in the generated template.

### Conditions

```bash
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:

```bash
# 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

```bash
# .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

```bash
# .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

```bash
# .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

1. Compilation variables (`--var`) - highest priority
2. Variables defined in the current file and imported files (in order)
3. System environment variables
4. Default values (`${VAR:-default}`)

### Precedence (first-wins)

By default, the first definition wins:

```bash
# .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:

- **POSIX shell escaping**: Uses Python's standard `shlex.quote()` function
- **PowerShell escaping**: Uses single-quoted literals with embedded quotes escaped
- **Variable name validation**: Only alphanumeric + underscore names are exported
- **Command injection prevention**: Dangerous values like `$(rm -rf /)` are safely escaped

```bash
# 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
- Treat `.env` and `.env-fuse` files as trusted configuration: they can expand
    process environment variables and import readable files.
- Use `--restrict-imports` to confine resolved imports, including symlinks, to
    the main source file's parent directory. External imports remain allowed by
    default for backward compatibility.
- On POSIX, `fuse` creates new output files with mode `0600` and preserves the
    permissions of existing output files.
- Use `--debug` to inspect resolved non-secret values before loading; secrets and
    their derived values remain masked: `dotenv-fusion load --debug`

## Python API

```python
from dotenv_fusion import DotenvLoader, load_dotenv, get_env

# Simple function; auto-detects .env-fuse, .env-fuse.local, then .env
values = load_dotenv(override=False, apply_types=True)

# Resolve dynamic imports and optionally search one parent directory
values = load_dotenv(compile_vars={"ENV": "prod"}, walk=1)

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

The names listed in `dotenv_fusion.__all__` form the supported public API.
Other module attributes remain implementation details. Shipping `py.typed`
describes the annotations of that public surface; it does not promote internal
helpers into the API.

## License

LGPL-3.0-or-later
