Secure API key and configuration management for Python developers
Project description
PySafeConfigX
Secure API key and configuration management for Python developers.
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 tool —
pysafeconfigx init,set,get,encrypt,doctor - Source-code scanner — detect accidentally committed secrets
- Fully typed — complete type hints, ships a
py.typedmarker - Zero magic — explicit, auditable, and predictable
Table of Contents
- Installation
- Quick Start
- Configuration Sources
- Encryption
- Multi-Environment
- CLI Reference
- API Reference
- Publishing to PyPI
- Contributing
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):
- OS environment variables
- Custom loaders (added via
add_loader(), in reverse order of registration) - Environment-specific .env file (e.g.
.env.production) - Base
.envfile
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
- Register an account at pypi.org
- 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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36a7feb348406afbb4bb9e49d082e892250fd99bb7613cdd9f026e42acc00f04
|
|
| MD5 |
858e1d3f9be6af3c436615f996d7c1da
|
|
| BLAKE2b-256 |
5cbd1e75ca45f1c981eb90a68f49427d768678cfa1915086d7d59226723b7e40
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82558afe24bc033f77bcd7e308158c6b17b17a8b1f71d70de07f37f4eb3e176a
|
|
| MD5 |
6e2c9317236c92f36316615afcdb6872
|
|
| BLAKE2b-256 |
dd2d5da03a768e49b87857efccb50a79c2396ed0501efb4e3582ed0a87672fd5
|