environment-guard 0.1.0
Zero-config environment variable validation - infer types from .env.example, validate at runtime
Requires Python: >=3.9
Keywords: config, configuration, dotenv, env, environment, type-checking, validation

# env-guard

**Zero-config environment variable validation for Python.**

Stop writing boilerplate. Your `.env.example` already documents your config — let it validate too.

## Why env-guard?

Most environment validation libraries require you to define your schema twice:

```python
# With Pydantic Settings (the old way)
class Settings(BaseSettings):
    PORT: int
    DEBUG: bool = False
    DATABASE_URL: str
    API_KEY: str
    REDIS_HOST: str = "localhost"
    REDIS_PORT: int = 6379
```

But you already have this information in `.env.example`:

```bash
PORT=8000
DEBUG=false
DATABASE_URL=postgresql://localhost/myapp
API_KEY=your-api-key-here
REDIS_HOST=localhost
REDIS_PORT=6379
```

**Why write it twice?**

With env-guard, you don't. One line validates everything:

```python
from env_guard import validate_env

validate_env()  # That's it. Zero config.
```

## Features

- **Zero configuration** — Types are inferred from `.env.example` values
- **Fail fast** — All errors reported at once with beautiful output
- **Type coercion** — Get typed values (`int`, `bool`, `float`, `str`)
- **Optional variables** — Mark with `# optional` comment
- **Rich output** — Colorized error reports (with fallback for minimal installs)
- **CI-friendly** — Exit code 1 on failure, works in any pipeline

## Installation

```bash
pip install environment-guard
```

For colorized output with Rich:

```bash
pip install environment-guard[rich]
```

## Quick Start

### 1. Create your `.env.example`

```bash
# .env.example
PORT=8000
DEBUG=false
DATABASE_URL=postgresql://localhost/myapp
API_KEY=your-secret-key
WEBHOOK_URL=https://example.com/hook  # optional
```

### 2. Validate on startup

```python
# app.py
from env_guard import validate_env

# Validates against .env.example, exits with code 1 on failure
validate_env()

# Your app starts here...
```

### 3. Get typed values (optional)

```python
from env_guard import validate

env = validate()

print(env["PORT"])        # 8000 (int, not str!)
print(env["DEBUG"])       # False (bool)
print(env["DATABASE_URL"]) # "postgresql://..." (str)
```

## Type Inference Rules

env-guard infers types from your example values:

| Example Value | Inferred Type |
|---------------|---------------|
| `8000` | `int` |
| `3.14` | `float` |
| `true`, `false`, `yes`, `no` | `bool` |
| Everything else | `str` |

## Optional Variables

Mark variables as optional with a comment:

```bash
REQUIRED_VAR=value
OPTIONAL_VAR=default  # optional
ALSO_OPTIONAL=123     # ?
NOT_REQUIRED=true     # not required
```

Optional variables won't cause validation failures if missing.

## API Reference

### `validate_env()`

Main validation function. Prints results and exits on failure.

```python
from env_guard import validate_env

result = validate_env(
    example_path=".env.example",  # Path to example file
    exit_on_error=True,           # Exit with code 1 on failure
    quiet=False,                  # Suppress output
)
```

### `validate()`

Returns typed values or raises `EnvValidationError`.

```python
from env_guard import validate, EnvValidationError

try:
    env = validate()
    port = env["PORT"]  # Already an int!
except EnvValidationError as e:
    print(f"Missing: {[err.var_name for err in e.result.errors]}")
```

### CLI

```bash
# Validate using .env.example in current directory
env-guard

# Specify a different file
env-guard -e config/.env.example

# Quiet mode (only show errors)
env-guard -q

# Don't exit with error code (useful for scripts)
env-guard --no-exit
```

## Error Output

When validation fails, you get a clear, grouped error report:

```
╭─────────────────────────────────────────────────────────────╮
│ Environment validation failed with 3 error(s)              │
╰─────────────────────────────────────────────────────────────╯

┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Missing Variables               ┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┫
┃ Variable        ┃ Expected Type┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┫
┃ API_KEY         ┃ string       ┃
┃ DATABASE_URL    ┃ string       ┃
┗━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┛

┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Type Mismatches                              ┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━╋━━━━━━━━━━━━━━━┫
┃ Variable        ┃ Expected ┃ Got           ┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━╋━━━━━━━━━━━━━━━┫
┃ PORT            ┃ integer  ┃ 'not-a-port'  ┃
┗━━━━━━━━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━━━━━━━━┛

Hint: Set missing variables or fix type mismatches in your environment.
```

## Integration Examples

### FastAPI

```python
# main.py
from fastapi import FastAPI
from env_guard import validate

# Validate before app creation
env = validate()

app = FastAPI()

@app.get("/")
def root():
    return {"port": env["PORT"], "debug": env["DEBUG"]}
```

### Flask

```python
# app.py
from flask import Flask
from env_guard import validate_env

validate_env()  # Fails fast if env is misconfigured

app = Flask(__name__)
```

### Django

```python
# settings.py
from env_guard import validate

env = validate()

DEBUG = env["DEBUG"]
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": env["DATABASE_NAME"],
    }
}
```

### Docker / CI

```dockerfile
# Dockerfile
FROM python:3.12-slim
COPY . /app
WORKDIR /app
RUN pip install .
# Validate env before starting
CMD ["sh", "-c", "env-guard && python main.py"]
```

```yaml
# GitHub Actions
- name: Validate environment
  run: env-guard
  env:
    PORT: 8000
    DEBUG: false
    API_KEY: ${{ secrets.API_KEY }}
```

## Comparison

| Feature | env-guard | pydantic-settings | environs |
|---------|-----------|-------------------|----------|
| Zero config | ✅ | ❌ | ❌ |
| Type inference | ✅ | ❌ | ❌ |
| Single source of truth | ✅ | ❌ | ❌ |
| Grouped error output | ✅ | ❌ | ❌ |
| No dependencies* | ✅ | ❌ | ❌ |
| Typed return values | ✅ | ✅ | ✅ |
| Nested config | ❌ | ✅ | ✅ |
| Complex validation | ❌ | ✅ | ✅ |

*Only requires `python-dotenv`. Rich is optional.

## When NOT to Use env-guard

env-guard is designed for simplicity. If you need:

- **Complex validation rules** (regex, min/max, custom validators)
- **Nested configuration** (YAML-style hierarchies)
- **Multiple environment files** (.env.local, .env.production)
- **Secret management integration** (AWS Secrets Manager, etc.)

Consider [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) or [dynaconf](https://www.dynaconf.com/) instead.

## Contributing

Contributions welcome! Please read our contributing guidelines first.

```bash
# Clone and install dev dependencies
git clone https://github.com/ljo3/env-guard.git
cd env-guard
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=env_guard
```

## License

MIT License - see [LICENSE](LICENSE) for details.