Skip to main content

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 .env or 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.environ as 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 .env in 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

notoriosti_env_manager-0.2.3.tar.gz (19.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

notoriosti_env_manager-0.2.3-py3-none-any.whl (21.6 kB view details)

Uploaded Python 3

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

Hashes for notoriosti_env_manager-0.2.3.tar.gz
Algorithm Hash digest
SHA256 1c17eac7ad18b628fd9db9411657933dd400879934cc5c686f1a5eb009819182
MD5 b642784b95501bff2fbe0ed7813bfdcf
BLAKE2b-256 674784517a645ff134c088b10984a2a7b275ccc99816310a01fd7d2f8e9dd8c4

See more details on using hashes here.

File details

Details for the file notoriosti_env_manager-0.2.3-py3-none-any.whl.

File metadata

File hashes

Hashes for notoriosti_env_manager-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5b06ff5da113d7f02685682b2467cc06253f497526ff1bca255949094e79515f
MD5 b8eddb760bda52cd44255462510e0515
BLAKE2b-256 96e90f599e18e1ef1cc6dbf5dbe9eac05c3da6ca1f5c3a926e49fc29bd7d2370

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page