Skip to main content

envgap

PyPI version Python versions CI License: MIT

Find gaps between .env, .env.example, shell variables, and Python code.

envgap is a diagnostic CLI for Python projects that use .env files, .env.example, shell variables, and os.environ / os.getenv in code. It does not load your config. It shows the gaps between what your app expects, what your project documents, and what your environment actually provides.

envgap detects Pydantic BaseSettings fields, aliases, and env prefixes for FastAPI-style projects.

envgap animated terminal demo

Why Developers Try It

  • Catch missing env vars before CI, Docker, or another developer's machine fails.
  • Spot stale .env.example files that no longer match real code.
  • Find typo-shaped drift like DB_URL vs DATABASE_URL.
  • Detect placeholder secrets such as changeme, todo, and your-key-here.
  • Add a lightweight config check to CI without adopting a new settings framework.

envgap is intentionally not a .env loader. It is the tool you run when you want the project to explain why config works in one place and breaks somewhere else.

Contents

30-Second Demo

Given a project with:

# .env
DB_URL=postgres://localhost/app
OPENAI_API_KEY=changeme
# app.py
import os

DATABASE_URL = os.environ["DATABASE_URL"]

Run:

$ DATABASE_URL=postgres://shell/app envgap check examples/basic

envgap check
===============

Checked:
  shell environment: available (1/4 expected key(s) found)
  .env: found (3 key(s))
  .env.example: found (3 key(s))
  Python code: 3 env usage(s)

Diagnosis:
  DATABASE_URL
    ! Missing value: DATABASE_URL is missing from .env (app.py:3)
      It is present in your shell environment, so local commands may work while CI or Docker still fails.
      Suggested fix: Add DATABASE_URL=... to .env or document how CI/Docker should provide it.

  DB_URL
    ~ Possible typo: DB_URL may be a typo for DATABASE_URL (.env:1)
      Suggested fix: Rename DB_URL to DATABASE_URL if they represent the same setting.

When It Helps

Use envgap when a Python project has config spread across:

  • local .env
  • documented .env.example
  • shell exports
  • Python code
  • CI or Docker conventions

It is especially useful for Python backend projects, FastAPI/Django/Flask apps, AI/data apps with API keys, and open-source projects where .env.example must stay useful for new contributors.

Why

Environment config bugs are boring until they eat an afternoon.

Common examples:

  • The app expects DATABASE_URL, but .env contains DB_URL.
  • .env.example says a key exists, but local .env never got it.
  • A secret is still set to changeme.
  • A variable works locally only because it is exported in your shell.
  • CI, Docker, or another developer's machine fails because the real required variables are not documented.

envgap is for that moment when you want the project to explain itself.

Install

With pip:

pip install envgap

With uv:

uv tool install envgap

With pipx:

pipx install envgap

From a local checkout:

pip install -e ".[dev]"

Quick Start

Run a check in the current project:

envgap check

Try the included broken example:

envgap check examples/basic

Try a FastAPI-style settings example:

envgap check examples/fastapi

Show machine-readable output:

envgap check --json

Ignore shell variables for deterministic CI checks:

envgap check --no-shell

Fail on warnings as well as errors:

envgap check --strict

--ci is supported as a CI-friendly alias for --strict:

envgap check --ci

Use custom dotenv filenames:

envgap check --env-file .env.local --example-file .env.example

What It Checks Today

envgap check currently inspects:

  • current shell environment
  • .env
  • .env.example
  • Python files using common environment variable APIs
  • Pydantic BaseSettings fields used by FastAPI-style settings modules

It detects:

  • missing keys
  • undocumented extra keys
  • duplicate keys
  • empty values
  • placeholder values like your-key-here, changeme, todo, and replace-me
  • likely typo pairs like DB_URL vs DATABASE_URL
  • required env vars used in Python code but missing from .env.example
  • required Pydantic settings fields missing from .env or .env.example
  • missing .env
  • missing .env.example

It scans Python code for:

os.environ["DATABASE_URL"]
os.getenv("DATABASE_URL")
os.getenv("DATABASE_URL", "sqlite:///local.db")
os.environ.get("DATABASE_URL", "sqlite:///local.db")

It also scans Pydantic Settings classes:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    openai_api_key: str
    debug: bool = False

Required vs optional behavior:

  • os.environ["KEY"] is required
  • os.getenv("KEY") is required
  • os.getenv("KEY", default) is optional
  • os.environ.get("KEY", default) is optional
  • BaseSettings fields without defaults are required
  • BaseSettings fields with defaults are optional
  • Field(alias=...) and Field(validation_alias=...) use the configured env name
  • simple env_prefix settings are applied to field names

Exit Codes

Command Exit code behavior
envgap check exits 1 when errors are present
envgap check --strict exits 1 when errors or warnings are present
envgap check --ci same as --strict
envgap check --json same pass/fail behavior, JSON output
envgap check --no-shell ignores current shell variables when diagnosing missing keys

Warnings do not fail a normal check unless --strict or --ci is used.

CI

Minimal GitHub Actions step:

- name: Check environment config drift
  run: |
    pip install envgap
    envgap check --strict

Full workflow:

name: envgap

on: [push, pull_request]

jobs:
  envgap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install envgap
      - run: envgap check --ci

pre-commit

Use envgap as a pre-commit hook to catch config drift before a change lands:

repos:
  - repo: https://github.com/Pinak-Datta/envgap
    rev: v0.2.2
    hooks:
      - id: envgap

Install pre-commit if you do not already have it:

pipx install pre-commit

Run it manually:

pre-commit run envgap --all-files

Example Diagnosis

Given:

# .env
DB_URL=postgres://localhost/app
OPENAI_API_KEY=changeme
# .env.example
DATABASE_URL=
OPENAI_API_KEY=your-key-here
# app.py
import os

DATABASE_URL = os.environ["DATABASE_URL"]
DEBUG = os.getenv("DEBUG", "false")

envgap can report:

  • DATABASE_URL is required in code but missing from .env
  • DB_URL may be a typo for DATABASE_URL
  • OPENAI_API_KEY still looks like a placeholder
  • DEBUG is optional because it has a default

Why Not Just python-dotenv?

python-dotenv loads environment variables.

envgap explains whether the environment variables your app expects match the variables your project defines and documents.

The useful question is not only:

Did .env load?

It is:

What does my app expect, where should it come from, and why is it missing or wrong here?

How envgap Differs from Other Tools

Tool Purpose How envgap differs
python-dotenv Loads .env files envgap diagnoses drift between code, .env, .env.example, and shell variables.
pydantic-settings Validates application settings envgap diagnoses configuration drift instead of validating settings.
django-environ Reads environment configuration for Django envgap is framework-independent and diagnoses configuration drift.
Secret scanners Detect leaked secrets envgap finds missing or inconsistent configuration rather than exposed secrets.

Current Scope

This is intentionally a small diagnostic tool, not a config framework.

In scope now:

  • .env
  • .env.example
  • shell environment
  • Python os.environ / os.getenv scanning
  • Pydantic BaseSettings field, alias, and env prefix detection
  • terminal and JSON reports
  • CI-friendly exit codes

Not in scope yet:

  • loading or mutating your environment
  • validating every framework-specific settings edge case
  • Docker Compose parsing
  • GitHub Actions secrets parsing
  • dynamic Pydantic settings config and nested settings

Roadmap

  • dynamic Pydantic settings config and nested settings
  • Django settings helper detection
  • Docker Compose env detection
  • GitHub Actions env/secrets detection
  • precedence explanations for shell vs .env vs framework defaults
  • GitHub Actions annotations
  • richer JSON schema for editor and CI integrations

See the full roadmap.

Contributing

Real-world config examples are the most useful contribution right now.

New contributors can start with good first issues, help wanted issues, or false positives from their own Python projects.

Good first contributions:

  • report a false positive with a tiny redacted example
  • add a missing framework pattern
  • improve docs for CI, FastAPI, Django, or Docker users
  • add tests for typo detection edge cases

See CONTRIBUTING.md and SUPPORT.md.

Development

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
pytest

Run the example locally:

envgap check examples/basic

Run the FastAPI-style example:

envgap check examples/fastapi

Run the shell-aware example:

DATABASE_URL=postgres://shell/app envgap check examples/basic

License

MIT

Metadata

Release files for envgap 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for envgap 0.2.2
File Size Uploaded
envgap-0.2.2.tar.gz 93.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for envgap 0.2.2
File Interpreter ABI Platform
envgap-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 113.0 kB

Release files / envgap-0.2.2.tar.gz

Download URL envgap-0.2.2.tar.gz
Size 93.5 kB
Tags Source
SHA-256 checksum
How to use checksums
e70b9efc3f4ea8b0a94dfac9705e08f64595021c6345bcd0b6a83adcef3bb4c8
BLAKE2b-256 checksum
How to use checksums
5f95f2e7dc6c7c100334d426ed9e761d502767b3633ca8793292b43c608f1830
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.0

Release files / envgap-0.2.2-py3-none-any.whl

Download URL envgap-0.2.2-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
521640340a96a34c7d27e30e6ac1290497e81f0ef931125559f48e3dc4b9ecb7
BLAKE2b-256 checksum
How to use checksums
ea7defde686b19cd95961b07510b2b8296194a1d02b9e18d8fa376d09c71b601
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.0

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page