envdoctor (Python)
Native Python port of envdoctor — a local-first consistency checker for environment variables, distributed on PyPI so Python projects can use it without Node.
See Why not X? for how envdoctor compares to dotenv-linter, gitleaks, and hosted secrets tools.
Install
pip install arun-envdoctor
The PyPI distribution is named
arun-envdoctor(PyPI blocksenvdoctoras too similar to an existing project), but the installed command and the importable package are both stillenvdoctor.
Quick start
envdoctor scan --dir . # audit; exit 1 on errors
envdoctor scan --strict # treat warnings as errors too
envdoctor scan --json # emit findings as a JSON array (no values)
What it detects
Reconciles the environment variables used in your Python source
(os.getenv("X"), os.environ.get("X"), os.environ["X"], and the
from os import environ forms) against those defined in your .env files,
then reports:
| Rule | Severity | Meaning |
|---|---|---|
undefined-in-source |
error | Referenced (source or infra files) but not defined in any .env file |
duplicates |
error | The same key is defined 2+ times within a single .env file |
public-prefix |
error | A secret-looking variable is exposed to client bundles via a public prefix (NEXT_PUBLIC_, VITE_, REACT_APP_, EXPO_PUBLIC_, GATSBY_, NUXT_PUBLIC_, VUE_APP_, PUBLIC_) |
type-mismatch |
error | A variable's value has incompatible inferred types across environments (e.g. PORT=3000 vs PORT=abc) |
unused |
warning | Defined in .env but never referenced in source |
environment-diff |
warning | Defined in some environment files but missing from others |
weak-secret |
warning | A secret-looking variable has a weak, empty, or placeholder value |
typo |
warning | A used-but-undefined name closely matches a defined one (likely a typo) |
In addition to Python source, envdoctor scans Docker Compose
(docker-compose.yml / compose.yaml), GitHub Actions workflows
(.github/workflows/*.yml), and Kubernetes manifests (any YAML with both
apiVersion: and kind:) for referenced variables. Detection is dependency-free
(regex only, no YAML parser): shell-style interpolation ${VAR} / $VAR
(including ${VAR:-default} forms) across all three, plus
${{ secrets.X }}, ${{ vars.X }}, and ${{ env.X }} references in Actions.
These references feed the same missing/undefined and unused detectors, so a
variable referenced only in infra files but never defined is flagged, and one
defined and referenced only in infra is not reported unused.
Comments and docstrings are stripped before scanning, so documented examples
don't cause false positives. Nothing is uploaded and variable values are
never printed — they are used only for detection and never appear in any output
(human or --json). envdoctor scan exits 1 when there are errors (or with
--strict, warnings), making it CI-friendly. Pass --json to emit a JSON array
of findings (each with rule, severity, name, message, file, line)
for machine consumption.
Library use
from pathlib import Path
from envdoctor import scan
result = scan(Path("."))
for finding in result.errors:
print(finding.name, finding.message)
Development
pip install -e ".[dev]" pytest
pytest
Subcommands
Alongside scan, every port shares these environment subcommands:
envdoctor diff <envA> <envB> # compare two environments (add --json)
envdoctor sync <from> <to> # copy missing keys (add --dry-run)
envdoctor init [--force] # generate .env.example + ENVIRONMENT.md
envdoctor fix # (re)generate both docs
diff reports which variable names are only in one environment; sync appends
the missing keys to the target .env file as empty KEY= placeholders — values
are never copied.
init / fix
Both commands generate two files at the project root from the union of every
variable name (defined in any .env* file ∪ referenced in source/infra), sorted
ascending. Values are never written.
.env.example— a header comment followed by oneNAME=line per variable.ENVIRONMENT.md— a Markdown table of each variable withDefined/Usedcolumns.
init writes each file only if it does not already exist (--force overwrites);
fix always regenerates both. Every port produces byte-identical files.
Schema validation
Add an envdoctor.schema.json at your project root to validate .env values:
{
"PORT": { "type": "integer", "min": 1, "max": 65535 },
"LEVEL": { "enum": ["debug", "info", "warn", "error"] },
"TOKEN": { "type": "string", "optional": true }
}
Supported rule fields: type (string/integer/float/boolean/url/json), enum,
regex, min, max, optional. Values that fail are reported as
schema-validation errors (values are never printed).
Help make envdoctor smarter
envdoctor is young and its detectors are opinionated. If it missed something or cried wolf, tell me:
Other languages
envdoctor ships as a standalone native port for each ecosystem:
- Node (reference) · Go · Ruby · PHP · Java · Perl
- 📖 Docs: arun-skg.github.io/envdoctor
- Main repository: github.com/arun-skg/envdoctor
Metadata
Release files for arun-envdoctor 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| arun_envdoctor-1.0.0.tar.gz | 15.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| arun_envdoctor-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.9 kB
Release files / arun_envdoctor-1.0.0.tar.gz
| Download URL | arun_envdoctor-1.0.0.tar.gz |
|---|---|
| Size | 15.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ff73a5e5b0697e8346005f03b444855df67b7dc99d9f362054bf1fea264fac7
|
|
BLAKE2b-256 checksum How to use checksums |
405965d373967674b1b63972e73d2fec761000289be6a4362f79f59a5c8257a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / arun_envdoctor-1.0.0-py3-none-any.whl
| Download URL | arun_envdoctor-1.0.0-py3-none-any.whl |
|---|---|
| Size | 13.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e1ff6d6f6a7f7e4b1991f68baf60dbfc52c7cc0eaee406b2f001a32a99d97db4
|
|
BLAKE2b-256 checksum How to use checksums |
cc7c7ace7fefda96cfd0188812b3580707badbcb49707d903ae2b9a778b9b272
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency log