Environment-aware configuration loader with GCP Secret Manager support
Project description
env-manager
A simple, environment-aware configuration manager that unifies secrets from local .env files and Google Cloud Secret Manager. It handles type coercion, validation, secret masking, and automatically loads variables to os.environ so external libraries work seamlessly.
Installation
Add to your pyproject.toml:
[project]
dependencies = [
"env-manager @ git+https://github.com/NotoriosTI/env-manager.git@main",
]
Then install with Poetry or pip.
Quickstart
Basic Usage (Recommended)
The singleton pattern is the simplest way to use env-manager:
# main.py - Initialize once at startup
from env_manager import init_config, get_config
init_config("config/config_vars.yaml")
# Now use anywhere in your codebase
db_password = get_config("DB_PASSWORD")
api_timeout = get_config("API_TIMEOUT", 30) # with default
What happens automatically:
- Variables are loaded from
.envor GCP Secret Manager - Type coercion is applied (strings → int/float/bool as configured)
- Values are validated (required vs optional)
- Everything is assigned to
os.environ - External libraries (LangChain, LangGraph, etc.) work automatically
Example:
# config_vars.yaml defines PORT as type: int
init_config("config/config_vars.yaml")
port = get_config("PORT") # → 8080 (actual int, not string)
# os.environ["PORT"] is now "8080" (string, for external libraries)
Secret Sources
By default, secrets are loaded from .env files. To use Google Cloud Secret Manager, set SECRET_ORIGIN=gcp.
Priority order:
- Explicit parameter:
init_config(..., secret_origin="gcp") - Environment variable:
export SECRET_ORIGIN=gcp .envfile:SECRET_ORIGIN=gcp- Default:
"local"
Configuration File
Create a YAML file (e.g., config_vars.yaml) with this structure:
# Optional: define named environments (selected via ENVIRONMENT env var)
environments:
production:
origin: gcp
gcp_project_id: my-gcp-project
local:
origin: local
dotenv_path: .env
default: true # Used when ENVIRONMENT is not set
variables:
# Required secret (must exist in .env or GCP)
DB_PASSWORD:
source: DB_PASSWORD
type: str
# Optional with fallback
PORT:
source: PORT
type: int
default: 8080
# Constant (no external source needed)
LOG_LEVEL:
type: str
default: "INFO"
# Per-variable origin override
ANALYTICS_KEY:
source: ANALYTICS_KEY
type: str
origin: gcp # Always fetch from GCP, regardless of global setting
dotenv_path: secrets/.env.gcp # Optional: custom .env path for this variable
validation:
strict: false
required:
- DB_PASSWORD # Error if missing
optional:
- DEBUG_MODE # Warning if missing
Variable Definition Rules
Each variable must have at least one of:
source: Name of the secret in.envor GCP Secret Managerdefault: Fallback value if not found
Optional per-variable overrides:
origin:"local"or"gcp"— overrides the globalsecret_originfor this variableenvironment: Name of a defined environment to use as the source context for this variabledotenv_path: Path to a custom.envfile for this variable (relative to project root)
Type coercion (type field):
str(default): No conversionint: Converts to integerfloat: Converts to floatbool: Accepts only"true","True","1","false","False","0"
Validation (validation section):
required: Raises error if variable is missingoptional: Logs warning if variable is missingstrict: true: Enforces all variables must have values (ignores defaults)
Environments (environments section):
- Named environments with their own
origin,dotenv_path, and/orgcp_project_id - Active environment selected via
ENVIRONMENTenv var - Mark one as
default: trueto use whenENVIRONMENTis not set
Complete API Reference
Singleton API (Recommended)
from env_manager import init_config, get_config, require_config
# Initialize once
init_config(
"config/config_vars.yaml",
secret_origin=None, # "local" or "gcp" (auto-detected if None)
gcp_project_id=None, # Required if secret_origin="gcp"
strict=None, # Override YAML strict setting
dotenv_path=None, # Custom .env path (auto-detected if None)
debug=False, # Show raw secrets in logs (NEVER in production)
)
# Use anywhere
value = get_config("KEY") # Returns value or None
value = get_config("KEY", "default") # Returns value or provided default
value = require_config("REQUIRED_KEY") # Raises RuntimeError if missing
Instance API (Advanced)
When to use ConfigManager directly:
- Multiple configurations simultaneously
- Advanced testing with dependency injection
- Complex microservices architectures
from env_manager import ConfigManager
manager = ConfigManager(
config_path="config/config_vars.yaml",
secret_origin=None, # "local" or "gcp" (falls back to env var)
gcp_project_id=None, # overrides discovery from env/.env
strict=None, # overrides YAML strict flag
auto_load=True, # eagerly load and validate
debug=False, # set True to log raw secret values
)
manager.get("DB_PASSWORD") # Returns value or None
manager.get("PORT", 8080) # Returns value or provided default
manager.require("API_KEY") # Raises RuntimeError if missing
manager.values # Dict of all loaded values
Low-level Loader API (Expert)
For custom integrations, the loader abstraction is also exported:
from env_manager import SecretLoader, create_loader
# Create a loader directly
loader = create_loader("local", dotenv_path=".env") # DotEnvLoader
loader = create_loader("gcp", gcp_project_id="my-project") # GCPSecretLoader
# Fetch secrets
values = loader.get_many(["DB_PASSWORD", "API_KEY"])
# → {"DB_PASSWORD": "secret", "API_KEY": "key123"}
create_loader raises ValueError for unsupported origins (anything other than "local" or "gcp").
Example: Multiple configurations
prod_config = ConfigManager("config/prod.yaml", secret_origin="gcp")
dev_config = ConfigManager("config/dev.yaml", secret_origin="local")
prod_db = prod_config.get("DB_PASSWORD")
dev_db = dev_config.get("DB_PASSWORD")
Example: Testing with dependency injection
class DatabaseService:
def __init__(self, config: ConfigManager = None):
self.config = config or ConfigManager("config/config_vars.yaml")
self.host = self.config.get("DB_HOST")
def test_database_service():
test_config = ConfigManager("config/test.yaml")
service = DatabaseService(config=test_config)
# Test in isolation
How It Works
Automatic Environment Loading
When you call init_config() or create a ConfigManager:
- Configuration is parsed from YAML
- Secrets are fetched from
.envor GCP Secret Manager - Types are coerced according to YAML definitions
- Values are validated (required/optional checks)
- Variables are assigned to
os.environautomatically - Secrets are masked in all log output
This means external libraries that read from os.environ (like LangChain, LangGraph, etc.) work automatically without any additional setup.
Type Coercion Details
When you use get_config(): You get the properly typed value
port = get_config("PORT") # → 8080 (int)
debug = get_config("DEBUG") # → False (bool)
When external libraries read os.environ: They get strings
os.environ["PORT"] # → "8080" (string)
os.environ["DEBUG"] # → "false" (string)
This is intentional - os.environ only stores strings, but external libraries handle string parsing correctly.
SECRET_ORIGIN Resolution
The SECRET_ORIGIN determines where to load secrets from:
Priority (highest to lowest):
- Explicit parameter:
init_config(..., secret_origin="gcp") - Environment variable:
export SECRET_ORIGIN=gcp .envfile:SECRET_ORIGIN=gcp(read without loading entire file)- Default:
"local"
Example:
# .env file
SECRET_ORIGIN=gcp
GCP_PROJECT_ID=my-project
# Automatically uses GCP from .env
init_config("config/config_vars.yaml")
GCP_PROJECT_ID Resolution
When using secret_origin="gcp", the GCP project ID is resolved with:
- Explicit parameter:
init_config(..., gcp_project_id="my-project") - Environment variable:
export GCP_PROJECT_ID=my-project .envfile:GCP_PROJECT_ID=my-project- Not set (warning logged)
Secret Masking
All secrets are automatically masked in logs for security:
- Short secrets (< 10 chars):
********** - Long secrets:
ab****1234(first 2 + last 4 chars shown)
Set debug=True to temporarily see raw values (never use in production):
init_config("config/config_vars.yaml", debug=True)
Migration Guide
If you're migrating from python-dotenv or manual os.environ usage:
- Copy
config_vars.yaml.exampleto your project - Customize the YAML with your variables
- Install env-manager in your dependencies
- Replace
load_dotenv()withinit_config("config/config_vars.yaml") - Replace
os.environ["KEY"]withget_config("KEY") - Configure
SECRET_ORIGIN=gcpandGCP_PROJECT_IDfor production
Before:
from dotenv import load_dotenv
import os
load_dotenv()
db_password = os.environ["DB_PASSWORD"]
port = int(os.environ.get("PORT", "8080"))
After:
from env_manager import init_config, get_config
init_config("config/config_vars.yaml")
db_password = get_config("DB_PASSWORD")
port = get_config("PORT") # Already an int, with default 8080
Troubleshooting
Configuration manager not initialised
- Call
init_config()before usingget_config()orrequire_config()
Missing GCP project ID
- Set
GCP_PROJECT_IDvia parameter, environment variable, or.envfile
Type coercion failed
- Check the YAML
typefield matches your value format - Ensure boolean values are exactly
"true","false","1", or"0"
Required variable not found
- Verify the secret exists in
.envor GCP Secret Manager - Check the secret name matches the YAML
sourcefield - Ensure GCP credentials have access to the project
Variables not loaded
- Confirm
init_config()was called successfully - Check logs for warnings or errors
- Verify
.envfile exists and is in the correct location
Development
# Install dependencies
poetry install
# Run tests
pytest -v
# Run with coverage
pytest --cov=env_manager --cov-report=html
The project uses Python 3.13+, Poetry for dependency management, and pytest for testing.
Related Projects
Looking for a TypeScript/Node.js version? Check out env-manager-js — a TypeScript implementation with full feature parity. Both projects share the same YAML config format, secret resolution logic, and API design, so you can use whichever fits your stack.
License
Copyright (c) 2025 NotoriosTI. All rights reserved.
This software is proprietary and confidential. Unauthorized copying, distribution, or use of this software, in whole or in part, is strictly prohibited.
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 notoriosti_env_manager-0.2.1.tar.gz.
File metadata
- Download URL: notoriosti_env_manager-0.2.1.tar.gz
- Upload date:
- Size: 20.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.14.3 Darwin/25.4.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
641249d285b795d6af9cc1ace98ecc4fceaf3f0f602c66983e81042dfab32f2b
|
|
| MD5 |
4650981c292452ab6fe715802ddee205
|
|
| BLAKE2b-256 |
992074c47dea4130e7963526f74120d1135ad1ad35f821ea2fb2e23e0bdba001
|
File details
Details for the file notoriosti_env_manager-0.2.1-py3-none-any.whl.
File metadata
- Download URL: notoriosti_env_manager-0.2.1-py3-none-any.whl
- Upload date:
- Size: 22.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.14.3 Darwin/25.4.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4858327522ef03d09cece0d81049aac62f56bb4f6cd3ccc7f4f20cea370f9e2
|
|
| MD5 |
d23d5424a11b92623b91124d4db93fba
|
|
| BLAKE2b-256 |
7bf9790a18d2973f599895044935daf7d6e6b745008f8105b77b46232ee4b004
|