Snapshot and diff Python runtime environments to debug parity issues
Project description
pycheckem
Snapshot and diff Python runtime environments to debug "works on my machine" parity issues across dev, staging, prod, and containers.
Zero external dependencies. Everything uses the Python standard library.
Install
pip install pycheckem
For color-coded terminal output (optional):
pip install pycheckem[pretty]
For TOML config support on Python < 3.11 (optional):
pip install pycheckem[toml]
For development:
git clone https://github.com/MateuszMarciszewski/Pycheckem.git
cd pycheckem
pip install -e ".[dev]"
Quick Start
Capture a snapshot on each machine, then diff them:
# On staging
pycheckem snapshot -o staging.json --label staging
# On prod
pycheckem snapshot -o prod.json --label prod
# Compare
pycheckem diff staging.json prod.json
Output:
pycheckem: staging vs prod
═══════════════════════════════════════════════
Packages (3 differences)
+ gunicorn 21.2.0
- debugpy 1.8.0
~ requests 2.31.0 → 2.28.0 ⚠ DOWNGRADE
Environment Variables (2 differences)
+ DATABASE_URL
~ LOG_LEVEL: DEBUG → WARNING
───────────────────────────────────────────────
Summary: 5 differences | Severity: MAJOR
Breaking: Package downgrade: requests 2.31.0 -> 2.28.0
CLI Reference
pycheckem snapshot
Capture the current environment to a JSON file.
pycheckem snapshot -o <output_file> [options]
| Flag | Description |
|---|---|
-o, --output |
Output file path (use - for stdout) |
--label |
Label for this snapshot (e.g. "staging", "prod") |
--config-files |
Config files to hash (e.g. .env, setup.cfg) |
--include-sensitive |
Include sensitive env vars (passwords, tokens, keys) |
What gets captured:
- Python — version, implementation, executable, prefix, platform
- Packages — all installed packages with versions, locations, dependencies
- Environment variables — filtered by default to exclude secrets
- OS — system, kernel, architecture, distro
- Paths —
sys.pathand$PATH - Config files — SHA-256 hash and top-level key inventory
- Project metadata — name, version, dependencies from
pyproject.toml/setup.cfg - Plugins — data from any registered pycheckem collector plugins
pycheckem diff
Compare two snapshot files.
pycheckem diff <snapshot_a> <snapshot_b> [options]
| Flag | Description |
|---|---|
--format |
Output format: ascii (default), json, rich, side-by-side / sbs |
--only |
Show only one section: packages, env, python, os, paths, config, project |
--exit-code |
Exit with code 1 if differences meet the severity threshold |
--fail-severity |
Minimum severity to trigger failure: minor (default), major, critical |
--ignore-packages |
Comma-separated packages to ignore (e.g. pip,setuptools,wheel) |
--ignore-env-vars |
Comma-separated env vars to ignore (e.g. HOSTNAME,PWD) |
--ignore-patterns |
Comma-separated regex patterns to ignore (e.g. .*_CACHE.*) |
pycheckem compare
Snapshot the current environment and diff against a saved snapshot in one step:
pycheckem compare <saved_snapshot> [options]
Supports all diff flags plus snapshot options (--label, --config-files, --include-sensitive).
# Quick check: does my local env match prod?
pycheckem compare prod.json
# CI: fail if current env drifts from baseline
pycheckem compare baseline.json --exit-code --fail-severity major
pycheckem history
Track environment snapshots over time:
# Add a snapshot to the history store
pycheckem history add snapshot.json
# List all snapshots in history
pycheckem history show
# Diff the last 2 snapshots in history
pycheckem history diff --last 2
# Diff with format and suppression options
pycheckem history diff --last 2 --format rich --ignore-packages pip,wheel
History is stored in .pycheckem/history/ with timestamped filenames. Use --dir to set a custom base directory.
pycheckem remote
Snapshot remote hosts via SSH and diff:
# Diff remote host against local environment
pycheckem remote user@prod-server
# Diff two remote hosts against each other
pycheckem remote user@staging user@prod
# With options
pycheckem remote user@host --timeout 60 --format rich --ignore-packages pip
Requires pycheckem to be installed on the remote host and ssh to be available locally.
CI Gating
Use --exit-code and --fail-severity to gate CI pipelines:
# Fail on any difference
pycheckem diff staging.json prod.json --exit-code
# Fail only on major or critical (ignore minor additions)
pycheckem diff staging.json prod.json --exit-code --fail-severity major
# Fail only on critical (OS mismatch, major version changes)
pycheckem diff staging.json prod.json --exit-code --fail-severity critical
Diff Suppression
Suppress noisy or expected differences via pyproject.toml or CLI flags:
# pyproject.toml
[tool.pycheckem]
ignore_packages = ["pip", "setuptools", "wheel"]
ignore_env_vars = ["HOSTNAME", "PWD", "SHLVL"]
ignore_patterns = [".*_CACHE.*"]
# Or inline via CLI flags
pycheckem diff a.json b.json --ignore-packages pip,setuptools --ignore-env-vars HOSTNAME
CLI flags merge with pyproject.toml config (they don't replace).
Output Formats
| Format | Flag | Description |
|---|---|---|
| ASCII | --format ascii |
Default terminal-friendly output |
| JSON | --format json |
Machine-readable for scripts and CI |
| Rich | --format rich |
Color-coded tables (requires pip install pycheckem[pretty]) |
| Side-by-side | --format sbs |
Two-column comparison |
pycheckem diff staging.json prod.json --format json | jq '.summary'
Python API
import pycheckem
# Capture a snapshot
snap = pycheckem.snapshot(label="dev")
# Save / load
pycheckem.save(snap, "dev.json")
loaded = pycheckem.load("dev.json")
# Diff two snapshots
snap_a = pycheckem.load("staging.json")
snap_b = pycheckem.load("prod.json")
result = pycheckem.diff(snap_a, snap_b)
# Inspect the result
print(result.summary.severity) # "identical", "minor", "major", "critical"
print(result.summary.total_differences)
print(result.summary.breaking_changes) # list of human-readable strings
# Render
from pycheckem.render import ascii, json, rich, side_by_side
print(ascii(result))
print(json(result))
print(rich(result)) # color-coded output (falls back to ascii if rich not installed)
print(side_by_side(result)) # two-column comparison
Snapshot Contents
The Snapshot object contains:
| Field | Type | Description |
|---|---|---|
metadata |
SnapshotMetadata |
Timestamp, hostname, label, pycheckem version |
python |
PythonInfo |
Version, implementation, executable, prefix, platform |
packages |
dict[str, PackageInfo] |
Installed packages with versions and dependencies |
env_vars |
dict[str, str] |
Environment variables (sensitive filtered by default) |
os_info |
OSInfo |
System, release, machine, distro |
paths |
PathInfo |
sys.path and $PATH entries |
config_files |
dict[str, ConfigFileInfo] |
Config file hashes and key inventories |
project |
ProjectInfo |
Project name, version, dependencies from pyproject.toml / setup.cfg |
plugins |
dict[str, dict] |
Data from registered collector plugins |
Diff Severity Levels
| Severity | Triggers |
|---|---|
identical |
No differences |
minor |
Added packages, env var changes, path changes, config changes, project metadata changes |
major |
Python minor version mismatch, package downgrades, removed packages, requires-python change |
critical |
Python major version change, OS/architecture mismatch, package major version change |
Plugins
Extend pycheckem with custom collectors by registering entry points:
# In your plugin's pyproject.toml
[project.entry-points."pycheckem.collectors"]
my_collector = "my_package.collector:collect"
The collector function receives no arguments and returns a dict. Plugin data is stored in the snapshot under the plugins key.
GitHub Actions
See docs/github-actions.md for a complete guide to CI integration, including:
- Baseline management with artifacts
- Severity threshold configuration
- Suppression rules in workflows
- PR comment integration
- History tracking in CI
Requirements
- Python >= 3.8
- No external dependencies (core)
- Optional:
pip install pycheckem[pretty]for color-coded output viarich - Optional:
pip install pycheckem[toml]forpyproject.tomlconfig on Python < 3.11
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pycheckem-0.3.0.tar.gz.
File metadata
- Download URL: pycheckem-0.3.0.tar.gz
- Upload date:
- Size: 49.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28c554c619e4c6cb31e6f65385a83fe7bde07fd54ba7908899ab32e6327f686f
|
|
| MD5 |
9ee6198d971e05207e6a2ad0943c2fee
|
|
| BLAKE2b-256 |
ee7f7fd3acf3cb40666984888172bfb47af1dafaa91bf2af5b2541e1cf93f479
|
File details
Details for the file pycheckem-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pycheckem-0.3.0-py3-none-any.whl
- Upload date:
- Size: 31.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
612c7892547b2fe1c65632a5e90d6f9cbf78db75dd9bb9096b89e1d29f9a9c35
|
|
| MD5 |
7c10c700a9d326ce5b229c4c16b9284c
|
|
| BLAKE2b-256 |
a78e8849c11adbcc0e37607f34757a3e088c1b63c89226de84e2a09c99c167fd
|