Metadata-Version: 2.4
Name: SafeConfigAlpha
Version: 1.0.0
Summary: Secure API key and configuration management for Python developers
Project-URL: Homepage, https://github.com/yourusername/safeconfig
Project-URL: Documentation, https://github.com/yourusername/safeconfig#readme
Project-URL: Repository, https://github.com/yourusername/safeconfig
Project-URL: Bug Tracker, https://github.com/yourusername/safeconfig/issues
Project-URL: Changelog, https://github.com/yourusername/safeconfig/CHANGELOG.md
Author-email: Your Name <you@example.com>
License: MIT License
        
        Copyright (c) 2024 Your Name
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: api-keys,configuration,dotenv,encryption,environment,security
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: click>=8.1.7
Requires-Dist: cryptography>=42.0.0
Requires-Dist: platformdirs>=4.2.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: pyyaml>=6.0.1
Requires-Dist: rich>=13.7.0
Provides-Extra: dev
Requires-Dist: mypy>=1.9.0; extra == 'dev'
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.12.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.3.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
Description-Content-Type: text/markdown

# SafeConfig

**Secure API key and configuration management for Python developers.**

[![CI](https://github.com/yourusername/safeconfig/actions/workflows/ci.yml/badge.svg)](https://github.com/yourusername/safeconfig/actions)
[![PyPI version](https://badge.fury.io/py/safeconfig.svg)](https://badge.fury.io/py/safeconfig)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
[![codecov](https://codecov.io/gh/yourusername/safeconfig/branch/main/graph/badge.svg)](https://codecov.io/gh/yourusername/safeconfig)

---

SafeConfig 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** — `safeconfig 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](#installation)
- [Quick Start](#quick-start)
- [Configuration Sources](#configuration-sources)
- [Encryption](#encryption)
- [Multi-Environment](#multi-environment)
- [CLI Reference](#cli-reference)
- [API Reference](#api-reference)
- [Publishing to PyPI](#publishing-to-pypi)
- [Contributing](#contributing)

---

## Installation

```bash
pip install safeconfig
```

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

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

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

---

## Quick Start

### 1. Initialise your project

```bash
safeconfig 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

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

### 3. Use in your application

```python
from safeconfig import SafeConfig

config = SafeConfig(
    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

```bash
safeconfig 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

SafeConfig 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

```python
from safeconfig import SafeConfig
from safeconfig.core.loaders import DotEnvLoader, JsonLoader, YamlLoader

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

### .env file

```dotenv
# .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 `.`)

```json
{
  "redis": { "host": "localhost", "port": 6379 },
  "s3":    { "bucket": "my-bucket", "region": "us-east-1" }
}
```

```python
config.get("redis.host")    # "localhost"
config.get_int("redis.port") # 6379
```

### YAML (same flattening rules)

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

---

## Encryption

SafeConfig 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

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

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

### Encrypt from the CLI

```bash
# Encrypt an existing plaintext value in .env (in place):
safeconfig encrypt SECRET_API_KEY

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

### Encrypt from Python

```python
config = SafeConfig(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
SafeConfig auto-decrypts them on `get()`.

---

## Multi-Environment

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

SafeConfig automatically loads `.env` **and** `.env.production`.  The
environment-specific file takes precedence.

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

---

## CLI Reference

```
Usage: safeconfig [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.
```

### `safeconfig init`

```bash
safeconfig init
safeconfig init -k DATABASE_URL -k API_KEY   # pre-populate template keys
safeconfig init --force                       # overwrite existing .env
```

### `safeconfig set`

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

### `safeconfig get`

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

### `safeconfig encrypt`

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

### `safeconfig doctor`

```bash
safeconfig doctor
safeconfig doctor --scan-dir src/    # scan specific directory for exposed secrets
```

---

## API Reference

### `SafeConfig`

```python
SafeConfig(
    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 |
| `SafeConfig.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](https://pypi.org/)
2. Install build tools: `pip install build twine`

### Manual publish

```bash
# 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/ safeconfig

# 5. Upload to PyPI
twine upload dist/*
```

### Automated publish via GitHub Actions

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

```bash
git tag v1.0.0
git push origin v1.0.0
```

Configure [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
in your PyPI project settings to avoid storing API tokens in GitHub Secrets.

---

## Contributing

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

# Run tests
pytest

# Run type checker
mypy src/safeconfig/

# Run linter
ruff check src/ tests/
```

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

---

## License

[MIT](LICENSE) © 2024 Your Name
