Environment-aware configuration loader with GCP Secret Manager support
Project description
env-manager
A Python 3.13+ configuration manager that unifies secrets from local .env files and Google Cloud Secret Manager. Handles type coercion, validation, secret masking, optional ECIES encryption, and automatically populates os.environ so external libraries work without extra setup.
Installation
# uv
uv add notoriosti-env-manager
# Poetry
poetry add notoriosti-env-manager
For encrypted .env file support:
uv add "notoriosti-env-manager[encrypted]"
poetry add "notoriosti-env-manager[encrypted]"
Quickstart
Initialize once at startup, use anywhere:
from env_manager import init_config, get_config
init_config("config/config_vars.yaml")
db_password = get_config("DB_PASSWORD")
api_timeout = get_config("API_TIMEOUT", 30) # with default
What happens automatically:
- Secrets are fetched from
.envor GCP Secret Manager - Types are coerced per YAML definitions (
str,int,float,bool) - Required/optional validation runs and logs warnings or raises errors
- All values are written to
os.environas strings - Secrets are masked in all log output
Configuration File
# Optional: 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
# With encrypted .env support
staging:
origin: local
dotenv_path: .env.staging
encrypted_dotenv:
enabled: true
private_key: # optional — omit to use env vars or .env.keys
source: DOTENV_PRIVATE_KEY # secret name containing the hex private key
secret_origin: gcp # 'local' or 'gcp'
gcp_project_id: my-gcp-project
variables:
DB_PASSWORD:
source: DB_PASSWORD # name in .env or GCP Secret Manager
type: str
PORT:
source: PORT
type: int
default: 8080
LOG_LEVEL:
type: str
default: "INFO" # constant — no external source needed
ANALYTICS_KEY:
source: ANALYTICS_KEY
type: str
origin: gcp # per-variable origin override
dotenv_path: secrets/.env.gcp # per-variable custom .env path
validation:
strict: false # true → all variables must resolve (ignores defaults)
required:
- DB_PASSWORD # raises ConfigValidationError if missing
optional:
- DEBUG_MODE # logs warning if missing
Variable fields
| Field | Description |
|---|---|
source |
Name in .env or GCP Secret Manager |
type |
str (default), int, float, bool |
default |
Fallback value if not found |
origin |
"local" or "gcp" — overrides global secret origin for this variable |
dotenv_path |
Custom .env path for this variable |
environment |
Named environment to use as source context |
Each variable must have at least one of source or default.
Boolean coercion accepts only: "true", "True", "1", "false", "False", "0".
Secret Origin Resolution
| Priority | Source |
|---|---|
| 1 | Explicit parameter: init_config(..., secret_origin="gcp") |
| 2 | SECRET_ORIGIN environment variable |
| 3 | SECRET_ORIGIN=gcp in .env file |
| 4 | Active environment's origin field |
| 5 | Default: "local" |
Origin Aliases
Both origin fields and the SECRET_ORIGIN environment variable accept canonical names or any of their aliases (case-insensitive):
| Canonical | Accepted aliases |
|---|---|
local |
dotenv, env-file, .env |
gcp |
gcp-secretmanager, gcp-secret-manager, secretmanager |
# These are all equivalent
environments:
dev:
origin: dotenv # same as local
prod:
origin: gcp-secretmanager # same as gcp
gcp_project_id: my-project
GCP Project ID Resolution
| Priority | Source |
|---|---|
| 1 | Explicit parameter: init_config(..., gcp_project_id="my-project") |
| 2 | GCP_PROJECT_ID environment variable |
| 3 | GCP_PROJECT_ID in .env file |
| 4 | Active environment's gcp_project_id field |
API Reference
Singleton API (recommended)
from env_manager import init_config, get_config, require_config
init_config(
"config/config_vars.yaml",
secret_origin=None, # "local" or "gcp" — auto-detected if None
gcp_project_id=None, # required when secret_origin="gcp"
strict=None, # overrides YAML strict setting
dotenv_path=None, # custom .env path — auto-detected if None
debug=False, # log raw secret values (never use in production)
)
get_config("KEY") # typed value or None
get_config("KEY", "default") # typed value or provided default
require_config("KEY") # typed value or raises RuntimeError
Instance API
For multiple configs, dependency injection, or testing:
from env_manager import ConfigManager
manager = ConfigManager(
config_path="config/config_vars.yaml",
secret_origin=None,
gcp_project_id=None,
strict=None,
auto_load=True,
dotenv_path=None,
debug=False,
)
manager.get("DB_PASSWORD")
manager.get("PORT", 8080)
manager.require("API_KEY")
manager.values # dict of all loaded values
Loader API
from env_manager import create_loader
loader = create_loader("local", dotenv_path=".env")
loader = create_loader("gcp", gcp_project_id="my-project")
values = loader.get_many(["DB_PASSWORD", "API_KEY"])
# → {"DB_PASSWORD": "secret", "API_KEY": "key123"}
Encrypted .env Files
env-manager supports dotenvx-compatible ECIES encryption (secp256k1 + AES-256-GCM). Encrypted files are safe to commit to source control.
Requires the [encrypted] extra.
Encrypting a file
# Encrypt .env in-place; writes private key to .env.keys
env-manager-encrypt .env
# With an environment name (writes DOTENV_PRIVATE_KEY_PRODUCTION to .env.keys)
env-manager-encrypt .env --env production
# Overwrite existing .env.keys
env-manager-encrypt .env --force
After encryption, .env values become encrypted:<base64> blobs and DOTENV_PUBLIC_KEY is written into the file header. The private key is written to .env.keys (same directory).
Decryption at load time
Decryption is automatic when enabled in config. Private key resolution order:
| Priority | Source |
|---|---|
| 1 | Explicit kwarg passed to create_loader |
| 2 | DOTENV_PRIVATE_KEY_<ENV> environment variable (when environment name is set) |
| 3 | DOTENV_PRIVATE_KEY environment variable |
| 4 | Colocated .env.keys file (same directory as .env) |
Enable via YAML:
environments:
production:
origin: local
dotenv_path: .env
encrypted_dotenv:
enabled: true
Or store the private key in GCP and let env-manager fetch it:
environments:
production:
origin: local
dotenv_path: .env
encrypted_dotenv:
enabled: true
private_key:
source: DOTENV_PRIVATE_KEY
secret_origin: gcp
gcp_project_id: my-gcp-project
Warning: Never
source .envin a shell when the file is encrypted. The shell assigns raw ciphertext strings — no decryption occurs.
Exceptions
from env_manager import DecryptionError, DecryptionIssue
try:
init_config("config/config_vars.yaml")
except DecryptionError as exc:
for issue in exc.issues: # list[DecryptionIssue]
print(issue.key, issue.message)
ConfigValidationError works the same way, with issues: list[ConfigValidationIssue] where each issue has variable and message fields.
Secret Masking
All secrets are masked in logs:
- Short secrets (< 10 chars):
********** - Long secrets:
ab****1234(first 2 + last 4 chars shown)
init_config("config/config_vars.yaml", debug=True) # shows raw values — never in production
Migration from python-dotenv
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, default 8080 from YAML
Troubleshooting
Configuration manager not initialised — call init_config() before get_config() or require_config().
Missing GCP project ID — set GCP_PROJECT_ID via parameter, env var, or .env.
Type coercion failed — check the type field in YAML matches your value format. Booleans must be exactly "true", "false", "1", or "0".
Required variable not found — verify the secret exists in .env or GCP, the name matches the source field, and GCP credentials have access.
eciespy is required — install the encrypted extra: uv add "notoriosti-env-manager[encrypted]".
FileExistsError: .env.keys already exists — use env-manager-encrypt .env --force to overwrite.
Development
uv sync
pytest -v
pytest --cov=env_manager --cov-report=html
Related Projects
env-manager-js — TypeScript implementation with full feature parity. Both share the same YAML config format and secret resolution logic.
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.3.tar.gz.
File metadata
- Download URL: notoriosti_env_manager-0.2.3.tar.gz
- Upload date:
- Size: 19.5 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 |
1c17eac7ad18b628fd9db9411657933dd400879934cc5c686f1a5eb009819182
|
|
| MD5 |
b642784b95501bff2fbe0ed7813bfdcf
|
|
| BLAKE2b-256 |
674784517a645ff134c088b10984a2a7b275ccc99816310a01fd7d2f8e9dd8c4
|
File details
Details for the file notoriosti_env_manager-0.2.3-py3-none-any.whl.
File metadata
- Download URL: notoriosti_env_manager-0.2.3-py3-none-any.whl
- Upload date:
- Size: 21.6 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 |
5b06ff5da113d7f02685682b2467cc06253f497526ff1bca255949094e79515f
|
|
| MD5 |
b8eddb760bda52cd44255462510e0515
|
|
| BLAKE2b-256 |
96e90f599e18e1ef1cc6dbf5dbe9eac05c3da6ca1f5c3a926e49fc29bd7d2370
|