envcheck-py 0.1.1
Validate environment variables at startup. Fail fast with clear errors.
License: MIT
Requires Python: >=3.8
Keywords: environment, env, validation, configuration, config, environment-variables, dotenv, settings

# 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-py
```

## 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.