π΅οΈ 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.envfile.
No more shipping to prod and realising you forgot STRIPE_API_KEY.
pip install envsleuth# 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 --jsonFound 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
Works with all three common patterns:
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") # requiredAlso handles aliased imports:
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.
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:
$ 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.
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.
GitHub Actions:
- name: Check env vars
run: |
pip install envsleuth
envsleuth scan --env .env.example --strictpre-commit:
# .pre-commit-config.yaml
- repo: local
hooks:
- id: envsleuth
name: envsleuth
entry: envsleuth scan --strict
language: system
pass_filenames: false| 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 |
--json |
JSON output for CI pipelines |
--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 |
| Flag | Description |
|---|---|
--path, -p |
Directory or file to scan. Default: . |
--output, -o |
Where to write. Default: ./.env.example |
--force, -f |
Overwrite existing output file |
--exclude, --ext |
Same as in scan |
| envsleuth | dotenv-linter | 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.
- click β CLI
- python-dotenv β
.envparsing - 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).
MIT
