Metadata-Version: 2.4
Name: envcheck-py
Version: 0.1.0
Summary: Validate environment variables at startup. Fail fast with clear errors.
Author: rajeshchandra
License: MIT
Project-URL: Homepage, https://github.com/rajeshchandra/envcheck
Project-URL: Repository, https://github.com/rajeshchandra/envcheck
Project-URL: Issues, https://github.com/rajeshchandra/envcheck/issues
Keywords: environment,env,validation,configuration,config,environment-variables,dotenv,settings
Classifier: Development Status :: 4 - Beta
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.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# envcheck

> Validate environment variables at startup. Fail fast with clear errors.

[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**envcheck** makes your environment variables:
- ✅ **Explicit** — declare what you need
- ✅ **Validated** — crash early, not in production
- ✅ **Typed** — get real Python types, not just strings
- ✅ **Self-documenting** — your schema IS the documentation

## Installation

```bash
pip install envcheck
```

## Quick Start

```python
from envcheck import env

# Define your environment contract
config = env({
    "DATABASE_URL": str,          # required string
    "DB_PORT": int,               # required, converted to int
    "DEBUG": bool,                # required, smart bool parsing
    "TIMEOUT": (float, 30.0),     # optional with default
    "LOG_LEVEL": (str, "INFO"),   # optional string with default
})

# Use with confidence
print(config.DATABASE_URL)  # Typed, validated, guaranteed
print(config.DB_PORT)       # Already an int
print(config.DEBUG)         # Already a bool
```

## What Happens When Validation Fails?

Your app crashes immediately with a clear error:

```
❌ EnvCheck Validation Failed

Missing required variables:
  • DATABASE_URL
  • API_KEY

Type validation errors:
  • DB_PORT: expected int, cannot convert 'not_a_number' to int
  • DEBUG: expected bool, cannot convert 'maybe' to bool (expected: true/false, 1/0, yes/no, on/off)

Fix these issues and restart the application.
```

**This is intentional.** Fail fast, fail loud, fail obviously.

## API Reference

### `env(schema) -> EnvConfig`

Validate environment variables against a schema.

**Schema Format:**

| Syntax | Meaning |
|--------|---------|
| `"VAR": str` | Required string |
| `"VAR": int` | Required integer |
| `"VAR": float` | Required float |
| `"VAR": bool` | Required boolean |
| `"VAR": (str, "default")` | Optional with default |
| `"VAR": (int, 8080)` | Optional int with default |

**Returns:** `EnvConfig` — an immutable object with attribute access.

### Type Conversion

| Env Value | Type | Result |
|-----------|------|--------|
| `"hello"` | `str` | `"hello"` |
| `"5432"` | `int` | `5432` |
| `"3.14"` | `float` | `3.14` |
| `"true"` | `bool` | `True` |
| `"false"` | `bool` | `False` |

### Boolean Parsing

envcheck accepts common boolean representations (case-insensitive):

| True Values | False Values |
|-------------|--------------|
| `true`, `t` | `false`, `f` |
| `yes`, `y` | `no`, `n` |
| `on` | `off` |
| `1` | `0` |

### EnvConfig Methods

```python
config = env({"VAR": str})

# Attribute access
config.VAR

# Dict-like access
config.get("VAR", "default")
config.to_dict()

# Iteration
for key in config:
    print(key, config.get(key))

# Check existence
"VAR" in config

# Length
len(config)
```

## Real-World Examples

### FastAPI Application

```python
# config.py
from envcheck import env

config = env({
    "DATABASE_URL": str,
    "REDIS_URL": str,
    "SECRET_KEY": str,
    "DEBUG": (bool, False),
    "PORT": (int, 8000),
    "WORKERS": (int, 4),
})

# main.py
from config import config
import uvicorn

if __name__ == "__main__":
    uvicorn.run("app:app", host="0.0.0.0", port=config.PORT, workers=config.WORKERS)
```

### Docker Compose

```yaml
# docker-compose.yml
services:
  app:
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/app
      - REDIS_URL=redis://redis:6379
      - SECRET_KEY=${SECRET_KEY}
      - DEBUG=false
```

### GitHub Actions

```yaml
# .github/workflows/test.yml
env:
  DATABASE_URL: postgres://localhost:5432/test
  DEBUG: true
  SECRET_KEY: test-secret
```

## Why envcheck?

### Before (what you're doing now)

```python
import os

db_url = os.getenv("DATABASE_URL")  # Could be None
port = os.getenv("DB_PORT", "5432")  # Still a string!
debug = os.getenv("DEBUG", "false") == "true"  # Fragile

# Somewhere later, your app crashes...
# "NoneType has no attribute 'split'"
```

### After (with envcheck)

```python
from envcheck import env

config = env({
    "DATABASE_URL": str,
    "DB_PORT": (int, 5432),
    "DEBUG": (bool, False),
})

# If we get here, everything is valid and typed
```

## Design Philosophy

1. **Zero dependencies** — just Python's stdlib
2. **One function** — `env()` does everything
3. **Fail fast** — crash immediately, not in production
4. **Be obvious** — clear errors, no magic
5. **Stay minimal** — ~200 lines of code total

## License

MIT License — use it however you want.
