Metadata-Version: 2.4
Name: envsleuth
Version: 0.2.0
Summary: Detective for env vars in Python code. Parses your source with AST, finds every os.getenv/os.environ usage, and tells you what's missing from your .env file.
Author: k38f
License: MIT
Project-URL: Homepage, https://github.com/k38f/envsleuth
Project-URL: Repository, https://github.com/k38f/envsleuth
Project-URL: Issues, https://github.com/k38f/envsleuth/issues
Keywords: env,dotenv,cli,static-analysis,ast,environment-variables
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click>=8.0
Requires-Dist: python-dotenv>=1.0
Requires-Dist: flashbar>=1.2
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# envsleuth

🌐 **English** · [简体中文](README.zh-CN.md)

*Parts of this README were translated and edited with AI.*

[![tests](https://github.com/k38f/envsleuth/actions/workflows/tests.yml/badge.svg)](https://github.com/k38f/envsleuth/actions/workflows/tests.yml)
[![pypi](https://img.shields.io/pypi/v/envsleuth.svg)](https://pypi.org/project/envsleuth/)
[![python](https://img.shields.io/pypi/pyversions/envsleuth.svg)](https://pypi.org/project/envsleuth/)
[![license](https://img.shields.io/pypi/l/envsleuth.svg)](LICENSE)

> 🕵️  The detective for env vars in Python code. Parses your source with AST, finds every `os.getenv()` / `os.environ[]` / `os.environ.get()`, and tells you what's missing from your `.env` file.

No more shipping to prod and realising you forgot `STRIPE_API_KEY`.


![envsleuth demo](demo.gif)


## Install

```bash
pip install envsleuth
```

## Usage

```bash
# scan current directory, check against ./.env
envsleuth scan

# specific directory, specific env file
envsleuth scan --path ./src --env .env.production

# CI mode — exits 1 if anything is missing
envsleuth scan --strict

# generate a .env.example from your code
envsleuth generate

# machine-readable output
envsleuth scan --json
```

### Example output

```
Found 6 variables in code
checking against .env

⚠️  AWS_SECRET — not in .env but has default in code (probably ok)
✅ DATABASE_URL
✅ DEBUG
❌ REDIS_URL — missing from .env
     at src/app.py:7
✅ SECRET_KEY
❌ STRIPE_API_KEY — missing from .env
     at src/app.py:6

⚠️  1 dynamic usage (variable name computed at runtime, can't check statically)
     src/app.py:12  →  getenv(name)

ℹ  1 variable in .env not referenced in code: UNUSED_VAR

3 ok  1 with default  2 missing
```

## What it detects

Works with all three common patterns:

```python
import os

a = os.getenv("A")              # required — must be in .env
b = os.getenv("B", "fallback")  # has default — warned but not required
c = os.environ["C"]             # required (would raise KeyError without)
d = os.environ.get("D")         # required
```

Also handles aliased imports:

```python
from os import getenv, environ
import os as sys_os

a = getenv("A")
b = environ["B"]
c = sys_os.getenv("C")
```

Variables with names computed at runtime (e.g. `os.getenv(f"PREFIX_{x}")`) can't be checked statically — they're reported in a separate warning section so you know they exist.

### Django and config libraries (new in 0.2)

envsleuth also understands the two most common third-party config patterns:

```python
# django-environ
import environ
env = environ.Env()
SECRET_KEY = env('SECRET_KEY')
DEBUG = env.bool('DEBUG', default=False)
DATABASES = {'default': env.db('DATABASE_URL')}
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])

# python-decouple
from decouple import config
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
```

All of `env('X')`, `env.bool('X')`, `env.int('X')`, `env.str('X')`,
`env.list('X')`, `env.float('X')`, `env.db('X')`, `env.cache('X')`,
`env.url('X')`, `env.path('X')`, `env.tuple('X')`, `env.dict('X')`,
`env.json('X')`, `env.search_url('X')`, `env.email_url('X')` are detected.
Aliased imports work too: `from decouple import config as cfg`.

## CI: GitHub Actions annotations

Get missing env vars surfaced as PR annotations on the exact source lines:

```yaml
# .github/workflows/env-check.yml
- name: Check env vars
  run: envsleuth scan --output github --strict
```

Each missing var becomes an `::error` annotation; dynamic lookups become
`::warning`. The format follows GitHub's [workflow command
spec](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions).

## pre-commit hook

Add envsleuth to your `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/k38f/envsleuth
    rev: v0.2.0
    hooks:
      - id: envsleuth
        # optional overrides
        # args: [--path, src, --env, .env]
```

Runs `envsleuth scan --strict` on every commit that touches Python files.
There's also an opt-in `envsleuth-generate` hook for regenerating
`.env.example` manually via `pre-commit run envsleuth-generate --hook-stage manual`.

## `envsleuth generate`

Scans your code and writes a `.env.example` with every variable found, a comment pointing at where it's used, and the default value from code if there is one:

```bash
$ envsleuth generate
Wrote 6 variables to .env.example

$ cat .env.example
# Generated by envsleuth — edit this file before committing.
# Each variable below is used somewhere in your code.

# used at src/app.py:8
AWS_SECRET=default-value

# used at src/app.py:3
DATABASE_URL=

# used at src/app.py:5
DEBUG=false
...
```

Use `--force` to overwrite an existing file, `--output path/to/file` to write elsewhere.

## `.envignore`

Exclude variables from the "missing" check with glob patterns — one per line:

```
# .envignore
TEST_*
LEGACY_*
DEBUG_TOOL
```

Great for vars that come from CI, Docker, or your shell rc files rather than the local `.env`.

## CLI reference

### `envsleuth scan`

| Flag | Description |
| --- | --- |
| `--path`, `-p` | Directory or file to scan. Default: `.` |
| `--env` | Path to `.env` file. Default: `./.env` |
| `--envignore` | Path to `.envignore`. Default: `./.envignore` if present |
| `--strict` | Exit with code 1 if vars are missing |
| `--output`, `-o` | `text` (default), `json`, or `github` (Actions annotations) |
| `--json` | Alias for `--output json` (kept for backwards compat) |
| `--no-color` | Disable ANSI colors (also honours `NO_COLOR` env var) |
| `--exclude DIR` | Extra directory name to skip. Can be repeated |
| `--ext .EXT` | Extra file extension to scan (e.g. `.pyi`). Can be repeated |
| `--verbose`, `-v` | Show usage locations for every variable |
| `--no-update-check` | Skip the weekly PyPI version check |

### `envsleuth generate`

| Flag | Description |
| --- | --- |
| `--path`, `-p` | Directory or file to scan. Default: `.` |
| `--output`, `-o` | Where to write. Default: `./.env.example` |
| `--force`, `-f` | Overwrite existing output file |
| `--no-color` | Disable ANSI colors in the success message |
| `--exclude`, `--ext` | Same as in `scan` |
| `--no-update-check` | Skip the weekly PyPI version check |

## Update notifications

envsleuth checks PyPI for new releases at most once per week. When a new version is available, it prints a single line to stderr:

```
ℹ  envsleuth 0.2.0 is available (you have 0.1.1). Run: pip install -U envsleuth
```

The check is cached, runs with a short timeout, and stays silent on any error (offline, blocked network, etc). To disable it entirely:

```bash
# per-command
envsleuth scan --no-update-check

# globally for your shell
export ENVSLEUTH_NO_UPDATE_CHECK=1
```

The cache lives at `~/.cache/envsleuth/last_check.json` (or `$XDG_CACHE_HOME/envsleuth/...`).

## How it compares

| | envsleuth | [dotenv-linter](https://github.com/dotenv-linter/dotenv-linter) | [python-decouple](https://github.com/HBNetwork/python-decouple) |
| --- | --- | --- | --- |
| Scans your **code** for env var usages | ✅ | ❌ | ❌ |
| Lints the **.env file itself** | ❌ | ✅ | ❌ |
| Runtime config reader with casting | ❌ | ❌ | ✅ |
| Generates `.env.example` from code | ✅ | ❌ | ❌ |
| Language | Python | Rust | Python |

envsleuth is the only tool here that understands your **source code**. The others either look at your `.env` file in isolation, or read env vars at runtime.

## Dependencies

- [click](https://click.palletsprojects.com/) — CLI
- [python-dotenv](https://github.com/theskumar/python-dotenv) — `.env` parsing
- [flashbar](https://github.com/k38f/flashbar) — progress bar (a tiny zero-dep lib I wrote; envsleuth uses it when scanning 20+ files)

The scanner itself uses only the Python standard library (`ast`).

## License

MIT
