Skip to main content

Secure API key and configuration management for Python developers

Project description

PySafeConfigX

Secure API key and configuration management for Python developers.

CI PyPI version Python 3.10+ License: MIT Ruff Checked with mypy codecov


PySafeConfigX solves the configuration management problem once and for all:

  • Multi-source loading — .env, JSON, YAML, and OS environment with priority merging
  • AES-256-GCM encryption — protect sensitive values at rest with one command
  • Multi-environment — development / staging / production out of the box
  • Required-key validation — fail fast at startup, not at runtime
  • CLI toolpysafeconfigx init, set, get, encrypt, doctor
  • Source-code scanner — detect accidentally committed secrets
  • Fully typed — complete type hints, ships a py.typed marker
  • Zero magic — explicit, auditable, and predictable

Table of Contents


Installation

pip install pysafeconfigx

For development (includes linting, type checking, and test tools):

git clone https://github.com/yourusername/pysafeconfigx
cd pysafeconfigx
pip install -e ".[dev]"
pre-commit install

Dependencies: cryptography, python-dotenv, pyyaml, click, rich, platformdirs


Quick Start

1. Initialise your project

pysafeconfigx init

This creates a .env file and generates a master encryption key. Copy the key to a safe location (password manager, secret vault).

🔑  Master Key
────────────────────────────────────────
Your new master key

abc123XYZ...  ← store this securely!
────────────────────────────────────────
✔  Created .env

2. Add your secrets

pysafeconfigx set DATABASE_URL postgres://localhost/mydb
pysafeconfigx set API_KEY sk-prod-very-secret --encrypt   # stored as AES-256 ciphertext

3. Use in your application

from pysafeconfigx import PySafeConfigX

config = PySafeConfigX(
    environment="production",
    required_keys=["DATABASE_URL", "API_KEY"],
)
config.load()

db_url  = config.get("DATABASE_URL")
api_key = config.get("API_KEY", decrypt=True)   # transparent decryption
workers = config.get_int("MAX_WORKERS", default=4)
debug   = config.get_bool("DEBUG", default=False)
hosts   = config.get_list("ALLOWED_HOSTS")

4. Diagnose problems

pysafeconfigx doctor
┌────────────────────────────────────────┬──────────┬───────────────────────────────┐
│ Check                                  │ Status   │ Detail                        │
├────────────────────────────────────────┼──────────┼───────────────────────────────┤
│ Master key (env var)                   │ ✔ OK     │ Valid key found in environment │
│ .env file (.env)                       │ ✔ OK     │ File found                    │
│ .gitignore                             │ ✔ OK     │ .env is ignored               │
│ Source code scan                       │ ✔ OK     │ No exposed secrets in 12 file  │
└────────────────────────────────────────┴──────────┴───────────────────────────────┘
All checks passed! Your configuration looks healthy.

Configuration Sources

PySafeConfigX merges configuration from multiple sources. Priority order (highest → lowest):

  1. OS environment variables
  2. Custom loaders (added via add_loader(), in reverse order of registration)
  3. Environment-specific .env file (e.g. .env.production)
  4. Base .env file
from pysafeconfigx import PySafeConfigX
from pysafeconfigx.core.loaders import DotEnvLoader, JsonLoader, YamlLoader

config = PySafeConfigX()
config.add_json("config/database.json")
config.add_yaml("config/features.yaml")
config.add_loader(DotEnvLoader(".env.local"))
config.load()

.env file

# .env
DATABASE_URL=postgres://localhost/mydb
API_KEY=enc:AQID...   # encrypted blob — auto-decrypted on get()
DEBUG=true
MAX_WORKERS=4
ALLOWED_HOSTS=localhost,127.0.0.1

JSON (nested keys are flattened with .)

{
  "redis": { "host": "localhost", "port": 6379 },
  "s3":    { "bucket": "my-bucket", "region": "us-east-1" }
}
config.get("redis.host")    # "localhost"
config.get_int("redis.port") # 6379

YAML (same flattening rules)

redis:
  host: localhost
  port: 6379
feature_flags:
  beta_signup: true

Encryption

PySafeConfigX uses AES-256-GCM (authenticated encryption) with PBKDF2-HMAC-SHA256 key derivation. Each encrypted value carries its own random salt and nonce, so encrypting the same plaintext twice always produces different ciphertexts.

Generate a master key

pysafeconfigx init          # generates a key and writes it to .env
# or:
python -c "from pysafeconfigx import PySafeConfigX; print(PySafeConfigX.generate_master_key())"

Store the key in SAFECONFIG_MASTER_KEY (env var or CI/CD secret). Never commit it.

Encrypt from the CLI

# Encrypt an existing plaintext value in .env (in place):
pysafeconfigx encrypt SECRET_API_KEY

# Set a new value as encrypted:
pysafeconfigx set STRIPE_SECRET sk_live_... --encrypt

Encrypt from Python

config = PySafeConfigX(master_key="your-key")
config.load()

blob = config.encrypt("PLAINTEXT_KEY")      # updates the in-memory store
value = config.get("PLAINTEXT_KEY", decrypt=True)

Encrypted blob format

<version:1B> <salt:16B> <nonce:12B> <ciphertext+tag:NB>

encoded as URL-safe base64. Values stored in .env have an enc: prefix so PySafeConfigX auto-decrypts them on get().


Multi-Environment

# Reads APP_ENV from the environment, defaulting to "development"
config = PySafeConfigX(environment="production", config_dir="config/")
config.load()

PySafeConfigX automatically loads .env and .env.production. The environment-specific file takes precedence.

Supported environments: development, staging, production, test, local.


CLI Reference

Usage: pysafeconfigx [OPTIONS] COMMAND [ARGS]...

Options:
  --env-file TEXT  Path to the .env file  [default: .env]
  -v, --verbose    Enable verbose logging
  --help           Show this message and exit.

Commands:
  init     Scaffold a .env file and generate a master encryption key.
  set      Set KEY to VALUE in the .env file.
  get      Print the value of KEY from the .env file.
  encrypt  Encrypt an existing plaintext value in the .env file.
  doctor   Diagnose common configuration issues.

pysafeconfigx init

pysafeconfigx init
pysafeconfigx init -k DATABASE_URL -k API_KEY   # pre-populate template keys
pysafeconfigx init --force                       # overwrite existing .env

pysafeconfigx set

pysafeconfigx set DATABASE_URL postgres://localhost/mydb
pysafeconfigx set SECRET sk_live_abc123 --encrypt
pysafeconfigx --env-file .env.production set LOG_LEVEL WARNING

pysafeconfigx get

pysafeconfigx get DATABASE_URL
pysafeconfigx get SECRET           # auto-decrypts enc: values
pysafeconfigx get SECRET --raw     # print encrypted blob without decrypting

pysafeconfigx encrypt

pysafeconfigx encrypt SECRET_API_KEY          # encrypt in-place in .env
pysafeconfigx encrypt SECRET --no-in-place   # print blob, don't modify file

pysafeconfigx doctor

pysafeconfigx doctor
pysafeconfigx doctor --scan-dir src/    # scan specific directory for exposed secrets

API Reference

PySafeConfigX

PySafeConfigX(
    environment="development",     # or read from APP_ENV
    required_keys=["DB_URL"],      # raise MissingRequiredKeyError if absent
    config_dir=".",                # where to look for .env files
    master_key=None,               # or read from SAFECONFIG_MASTER_KEY
    auto_load_env=True,            # auto-register .env and .env.<environment>
    strict=False,                  # raise KeyNotFoundError instead of returning None
)
Method Description
.load(force=False) Load all sources and validate required keys
.reload() Force a full reload
.get(key, default, decrypt=False) Get a value
.require(key, decrypt=False) Get a value, raise if absent
.get_int(key, default) Get as integer
.get_bool(key, default) Get as boolean
.get_list(key, default, separator=",") Get as list
.set(key, value, encrypt=False) Set in-memory value
.encrypt(key) Encrypt in-place in the in-memory store
.add_loader(loader) Register a custom loader
.add_json(path) Add a JSON source
.add_yaml(path) Add a YAML source
.all() Return all loaded key-value pairs
.keys() Return sorted list of keys
PySafeConfigX.generate_master_key() Generate a new master key

Exceptions

Exception Raised when
ConfigurationError Base class for all library errors
KeyNotFoundError Key not found (in strict mode or via .require())
MissingRequiredKeyError Required key absent after load
EncryptionError Encryption failed
DecryptionError Decryption failed (wrong key or corrupted value)
InvalidEnvironmentError Unknown environment name
MasterKeyError Master key missing or malformed
ExposedKeyWarning Hard-coded secret detected in source (warning, not error)

Publishing to PyPI

Prerequisites

  1. Register an account at pypi.org
  2. Install build tools: pip install build twine

Manual publish

# 1. Bump version in pyproject.toml
# 2. Build distributions
python -m build

# 3. Upload to TestPyPI first (recommended)
twine upload --repository testpypi dist/*

# 4. Test the installation
pip install --index-url https://test.pypi.org/simple/ pysafeconfigx

# 5. Upload to PyPI
twine upload dist/*

Automated publish via GitHub Actions

The included CI workflow publishes automatically when you push a version tag:

git tag v1.0.0
git push origin v1.0.0

Configure PyPI Trusted Publishing in your PyPI project settings to avoid storing API tokens in GitHub Secrets.


Contributing

# Clone and set up
git clone https://github.com/yourusername/pysafeconfigx
cd pysafeconfigx
pip install -e ".[dev]"
pre-commit install

# Run tests
pytest

# Run type checker
mypy src/pysafeconfigx/

# Run linter
ruff check src/ tests/

PRs are welcome! Please open an issue first to discuss substantial changes.


License

MIT © 2024 Your Name

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

pysafeconfigx-1.0.0.tar.gz (28.9 kB view details)

Uploaded Source

Built Distribution

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

pysafeconfigx-1.0.0-py3-none-any.whl (29.9 kB view details)

Uploaded Python 3

File details

Details for the file pysafeconfigx-1.0.0.tar.gz.

File metadata

  • Download URL: pysafeconfigx-1.0.0.tar.gz
  • Upload date:
  • Size: 28.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pysafeconfigx-1.0.0.tar.gz
Algorithm Hash digest
SHA256 36a7feb348406afbb4bb9e49d082e892250fd99bb7613cdd9f026e42acc00f04
MD5 858e1d3f9be6af3c436615f996d7c1da
BLAKE2b-256 5cbd1e75ca45f1c981eb90a68f49427d768678cfa1915086d7d59226723b7e40

See more details on using hashes here.

File details

Details for the file pysafeconfigx-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pysafeconfigx-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 29.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pysafeconfigx-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 82558afe24bc033f77bcd7e308158c6b17b17a8b1f71d70de07f37f4eb3e176a
MD5 6e2c9317236c92f36316615afcdb6872
BLAKE2b-256 dd2d5da03a768e49b87857efccb50a79c2396ed0501efb4e3582ed0a87672fd5

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