Metadata-Version: 2.3
Name: envgate
Version: 0.7.0
Summary: A minimal Python library to validate environment variables at startup. Zero dependencies.
Keywords: environment,validation,env,config,startup
Author: Victor Magueta Soler
Author-email: Victor Magueta Soler <solermvictor@gmail.com>
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Project-URL: Homepage, https://github.com/vmagueta/envgate
Project-URL: Repository, https://github.com/vmagueta/envgate
Project-URL: Issues, https://github.com/vmagueta/envgate/issues
Description-Content-Type: text/markdown

# envgate

[![CI](https://github.com/vmagueta/envgate/actions/workflows/ci.yml/badge.svg)](https://github.com/vmagueta/envgate/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/vmagueta/envgate/branch/main/graph/badge.svg)](https://codecov.io/gh/vmagueta/envgate)
[![PyPI version](https://img.shields.io/pypi/v/envgate)](https://pypi.org/project/envgate/)
[![Python](https://img.shields.io/pypi/pyversions/envgate)](https://pypi.org/project/envgate/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

A minimal Python library to validate environment variables at startup. Zero dependencies.

## Why?

Instead of your app crashing at runtime because `DATABASE_URL` is missing,
envgate validates everything at startup and tells you exactly what's wrong.

## Installation

```bash
pip install envgate
```

## Quick Start

```python
from envgate import get_env, validate

# Get a single variable with type coercion
port = get_env("PORT", type="int", default=8000)
debug = get_env("DEBUG", type="bool", default=False)

# Explicitly mark a variable as required
api_key = get_env("API_KEY", required=True)

# Parse comma-separated lists (or use a custom separator)
hosts = get_env("ALLOWED_HOSTS", type="list")            # ["a", "b", "c"]
ports = get_env("PORTS", type="list[int]", sep=":")      # [8000, 8001]

# Or validate multiple variables at once
config = validate({
    "DATABASE_URL": {"type": "str"},
    "REDIS_URL": {"type": "str"},
    "PORT": {"type": "int", "default": 8000},
    "DEBUG": {"type": "bool", "default": False},
})
```

If `DATABASE_URL` and `REDIS_URL` are missing and `PORT` is invalid, you get all errors at once:

```
envgate.exceptions.ValidationError: Environment validation failed:
    - Environment variable 'DATABASE_URL' is not set.
    - Environment variable 'REDIS_URL' is not set.
    - Environment variable 'PORT' has invalid value 'abc' (expected int).
```

## Custom validators

Type coercion checks that `PORT` is an integer. A `validator` checks that
the value also makes sense — e.g. that the port is in a usable range, or
that a log level is one of a fixed set:

```python
def in_port_range(p):
    if not (1024 <= p <= 65535):
        raise ValueError("must be in [1024, 65535]")

def is_known_level(level):
    if level not in {"debug", "info", "warning", "error"}:
        raise ValueError("must be one of debug|info|warning|error")

config = validate({
    "PORT": {"type": "int", "validator": in_port_range},
    "LOG_LEVEL": {"type": "str", "default": "info", "validator": is_known_level},
})
```

A validator signals failure by raising any exception — its message is
captured and joined into the same collective `ValidationError` as missing
and invalid-type errors:

```
envgate.exceptions.ValidationError: Environment validation failed:
    - Environment variable 'PORT' has invalid value '80': must be in [1024, 65535]
    - Environment variable 'LOG_LEVEL' has invalid value 'verbose': must be one of debug|info|warning|error
```

## Loading a `.env` file

For local development, load variables from a `.env` file before validating.
`load_env()` copies the file's entries into `os.environ`, so `validate()`
picks them up with no extra wiring:

```python
from envgate import load_env, validate

load_env()  # reads ./.env into os.environ (defaults to ".env")

config = validate({
    "DATABASE_URL": {"type": "str"},
    "PORT": {"type": "int", "default": 8000},
})
```

Given a `.env` like:

```
# database
DATABASE_URL=postgres://localhost/app
PORT=5432
export DEBUG="true"
```

- **Real environment variables always win.** A key already set in the
  environment (CI, containers, systemd) is never overwritten by the file.
- **A missing file is a silent no-op** — handy in production, where you
  rely on real environment variables and ship no `.env`. A file that
  *exists* but has a broken line raises `EnvFileError`.
- `load_env()` returns a dict of everything it parsed from the file, so
  you can log or inspect it without touching global state.

Parsing is stdlib-only and deliberately simple: blank lines and full-line
`#` comments are skipped, a leading `export ` is tolerated, surrounding
quotes are stripped, and there's no shell-style interpolation.

## Supported Types

| Type | Example values |
|------|---------------|
| `str` | Any string (default) |
| `int` | `"42"`, `"-7"`, `"0"` |
| `float` | `"3.14"`, `"42"`, `"-2.5"` |
| `bool` | `"true"`, `"1"`, `"yes"`, `"on"` / `"false"`, `"0"`, `"no"`, `"off"` |
| `list`, `list[str]`, `list[int]`, `list[float]`, `list[bool]` | Comma-separated values — e.g. `"a,b,c"` → `["a", "b", "c"]`. Pass `sep=":"` (or any character) to override the separator. |

## Contributing

Contributions are welcome! Check out the [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

MIT
