Skip to main content

envresolve

Resolve environment variables from secret stores like Azure Key Vault.

Features

  • Variable expansion: Expand ${VAR} and $VAR syntax in strings
  • Secret resolution: Fetch secrets from Azure Key Vault (more providers coming)
  • Circular reference detection: Prevents infinite loops in variable chains
  • Type-safe: Full mypy type checking support

Quick Start

Variable Expansion

Expand variables without connecting to external services:

from envresolve import expand_variables

env = {"VAULT": "corp-kv", "SECRET": "db-password"}
result = expand_variables("akv://${VAULT}/${SECRET}", env)
print(result)  # akv://corp-kv/db-password

Default Values

Use default values for undefined or empty variables with ${VAR:-default} syntax:

from envresolve import expand_variables

# Variable is undefined - use default
result = expand_variables("${HOST:-localhost}", {})
print(result)  # localhost

# Variable is empty - use default (Bash :- semantics)
result = expand_variables("${HOST:-localhost}", {"HOST": ""})
print(result)  # localhost

# Variable is defined - ignore default
result = expand_variables("${HOST:-localhost}", {"HOST": "example.com"})
print(result)  # example.com

# Defaults can contain nested variables
env = {"FALLBACK_HOST": "backup.example.com"}
result = expand_variables("${HOST:-${FALLBACK_HOST}}", env)
print(result)  # backup.example.com

# Multiple defaults in one string
result = expand_variables("${PROTO:-https}://${HOST:-localhost}:${PORT:-443}", {})
print(result)  # https://localhost:443

Load from .env File

Load environment variables from a .env file with automatic secret resolution:

import envresolve

# .env file content:
# VAULT_NAME=my-vault
# DATABASE_URL=akv://${VAULT_NAME}/db-url
# API_KEY=akv://${VAULT_NAME}/api-key

# Requires: pip install envresolve[azure]
# Requires: Azure authentication (az login, Managed Identity, etc.)
envresolve.register_azure_kv_provider()

# Load .env and resolve all secret URIs
# By default, searches for .env in current directory and exports to os.environ
resolved_vars = envresolve.load_env()

# Or specify explicit path and disable export
resolved_vars = envresolve.load_env(dotenv_path=".env", export=False)

Note: For complete python-dotenv compatibility, use load_dotenv() + resolve_os_environ():

from dotenv import load_dotenv
import envresolve

# Use python-dotenv's search behavior (from calling script location)
load_dotenv()

# Resolve secrets in os.environ
envresolve.register_azure_kv_provider()
envresolve.resolve_os_environ()

Direct Secret Resolution

Fetch individual secrets from Azure Key Vault:

import envresolve

# Requires: pip install envresolve[azure]
# Requires: Azure authentication (az login, Managed Identity, etc.)
try:
    envresolve.register_azure_kv_provider()
    secret_value = envresolve.resolve_secret("akv://corp-vault/db-password")
    print(secret_value)
except envresolve.ProviderRegistrationError as e:
    print(f"Azure SDK not available: {e}")
except envresolve.SecretResolutionError as e:
    print(f"Failed to fetch secret: {e}")

Custom Provider Configuration

Inject custom provider instances for advanced scenarios (testing, custom credentials, etc.):

import envresolve
from envresolve.providers.azure_kv import AzureKVProvider
from azure.identity import ManagedIdentityCredential

# Create custom provider with specific credential
custom_provider = AzureKVProvider(
    credential=ManagedIdentityCredential(client_id="your-client-id")
)

# Register the custom provider
envresolve.register_azure_kv_provider(provider=custom_provider)

# Now use envresolve as normal
secret = envresolve.resolve_secret("akv://vault/secret")

This is particularly useful for:

  • Testing: Inject mock providers without patching internal implementation details
  • Custom authentication: Use specific Azure credentials (service principal, managed identity, etc.)
  • Provider configuration: Pre-configure providers with custom settings

Resolve Existing Environment Variables

Resolve secret URIs already set in os.environ (useful for containerized applications):

import os
import envresolve

# Environment variables set by container orchestrator or parent process
os.environ["API_KEY"] = "akv://prod-vault/api-key"
os.environ["DB_PASSWORD"] = "akv://prod-vault/db-password"

# Requires: pip install envresolve[azure]
envresolve.register_azure_kv_provider()

# Resolve all environment variables containing secret URIs
resolved = envresolve.resolve_os_environ()

# Resolve only specific keys
resolved = envresolve.resolve_os_environ(keys=["API_KEY"])

# Resolve variables with prefix and strip the prefix
# DEV_API_KEY -> API_KEY, DEV_DB_URL -> DB_URL
os.environ["DEV_API_KEY"] = "akv://dev-vault/api-key"
os.environ["DEV_DB_URL"] = "akv://dev-vault/db-url"
resolved = envresolve.resolve_os_environ(prefix="DEV_")

# Ignore specific variables (exact match)
os.environ["PS1"] = "${USER}@${HOST}$ "  # Should not be expanded
os.environ["API_KEY"] = "akv://vault/api-key"
resolved = envresolve.resolve_os_environ(ignore_keys=["PS1"])

# Ignore variables by pattern (glob matching)
os.environ["PS1"] = "${USER}@${HOST}$ "
os.environ["PS2"] = "> "
os.environ["PROMPT"] = "${PWD}$ "
os.environ["API_KEY"] = "akv://vault/api-key"
resolved = envresolve.resolve_os_environ(ignore_patterns=["PS*", "PROMPT*"])

# Combine exact match and patterns
resolved = envresolve.resolve_os_environ(
    ignore_keys=["SPECIFIC_VAR"],
    ignore_patterns=["TEMP_*", "DEBUG_*"]
)

Error Handling

Resolution errors include context about which environment variable failed:

import envresolve

try:
    envresolve.load_env(dotenv_path=".env", export=False)
except envresolve.EnvironmentVariableResolutionError as e:
    print(f"Failed variable: {e.context_key}")
    print(f"Cause: {e.original_error}")

Control error behavior:

# Skip variables with expansion errors
resolved = envresolve.load_env(stop_on_expansion_error=False)

# Skip variables with secret resolution errors
resolved = envresolve.load_env(stop_on_resolution_error=False)

stop_on_expansion_error=False also skips variables whose values use ${...} syntax that envresolve does not support (for example bash's ${VAR:+word}, as found in the default Debian/Ubuntu PS1). This is useful when resolving os.environ, which may contain shell-provided variables that were never meant to be expanded:

import os
import envresolve

# Default Debian/Ubuntu PS1 uses unsupported ${VAR:+word} syntax
os.environ["PS1"] = "${debian_chroot:+($debian_chroot)}"

# PS1 is skipped; the remaining variables are still resolved
resolved = envresolve.resolve_os_environ(stop_on_expansion_error=False)

Installation

# Basic installation (variable expansion only)
pip install envresolve

# With Azure Key Vault support
pip install envresolve[azure]

Documentation

Full documentation: https://osoekawaitlab.github.io/envresolve/

Development

Setup

This project uses uv for dependency management and nox for task automation:

# Install uv (if not already installed)
pip install uv

# Clone the repository
git clone https://github.com/osoekawaitlab/envresolve.git
cd envresolve

# Install dependencies and create virtual environment
uv sync --all-extras --all-groups

# Activate virtual environment
source .venv/bin/activate  # On Unix/macOS
# .venv\Scripts\activate   # On Windows

Running Tests

# Quick test during development
nox -s tests_unit      # Unit tests only (fast)
nox -s tests_e2e       # E2E tests with mocked Azure SDK

# Full test suite
nox -s tests           # All tests with coverage report (HTML in htmlcov/)

# Test across Python versions
nox -s tests_all_versions  # Test on Python 3.10-3.14

# Test without Azure SDK
nox -s tests_without_azure  # For environments without Azure dependencies

Code Quality

# Run all quality checks
nox -s quality         # Type checking (mypy) + linting (ruff)

# Individual checks
nox -s mypy            # Type checking only
nox -s lint            # Linting only
nox -s format_code     # Auto-format code

# Run everything
nox -s check_all       # Tests + quality checks

Live Azure Tests

Optional integration tests against real Azure Key Vault:

# One-time setup
cd infra/terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your Azure credentials
terraform init
terraform apply

# Before running tests
az login
source scripts/setup_live_tests.sh

# Run live tests
nox -s tests_live

See Live Azure Tests documentation for detailed setup instructions.

Build Documentation

# Build documentation
nox -s docs_build

# Serve documentation locally (with live reload)
mkdocs serve           # Open http://localhost:8000

Contributing

See Contributing Guide for guidelines on:

  • Code style and conventions
  • Test-driven development workflow
  • Creating issues and pull requests
  • Architecture Decision Records (ADRs)

Metadata

Release files for envresolve 0.1.12

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for envresolve 0.1.12
File Size Uploaded
envresolve-0.1.12.tar.gz 21.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for envresolve 0.1.12
File Interpreter ABI Platform
envresolve-0.1.12-py3-none-any.whl Python 3 none any Details

Total release size: 43.2 kB

Release files / envresolve-0.1.12.tar.gz

Download URL envresolve-0.1.12.tar.gz
Size 21.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ed087717ed01407af1cd8bb08e40f0956cce6a21a53f16c828725f065019405c
BLAKE2b-256 checksum
How to use checksums
6fe25ca5a603853b5d08f02babf59847296d8c2c03ad429b9e59a97b09ea6798
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 3, 2026.

Transparency log

Release files / envresolve-0.1.12-py3-none-any.whl

Download URL envresolve-0.1.12-py3-none-any.whl
Size 21.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1318c375f6f2ecf48a9e6077621d8453fa39f0121377ad2c1bc6e7c1bf126e0f
BLAKE2b-256 checksum
How to use checksums
63980156b16780790af833dcc667f515873b108ea52e712a3055acef9a36d211
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.12 This release

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page