Skip to main content

compat-check

test

Check whether a GitHub repo or PyPI package would install cleanly in this environment — before you actually install it.

$ compat-check https://github.com/pallets/flask
compat-check: https://github.com/pallets/flask
backend: uv
requirements checked: blinker>=1.9.0, click>=8.1.3, itsdangerous>=2.2.0, jinja2>=3.1.2, markupsafe>=2.1.1, werkzeug>=3.1.0

OK — 6 package(s) would install cleanly:
  + blinker==1.9.0
  + click==8.5.0
  ...
$ compat-check some-package-with-a-real-conflict
PROBLEMS FOUND — 1 package(s) cannot be resolved:

  [numpy]
      × No solution found when resolving dependencies:
      ╰─▶ Because you require numpy>=2.0 and numpy<1.20, we can conclude that your
          requirements are unsatisfiable.

Why this instead of uv/pip themselves

uv and pip already resolve dependencies — that's not the gap. Two things are:

  • You still have to actually run the install (or a dry-run) yourself, reading whatever error comes back. compat-check does that in a throwaway venv and hands you a plain-language pass/fail, without touching your real environment.
  • Both resolvers are fail-fast: a single dry-run call reports only the first unsatisfiable requirement. If two unrelated packages in the same requirements.txt are both broken, one hides behind the other. compat-check drops each failure and retries until every one surfaces.

It does not try to out-resolve uv/pip — it wraps them (preferring uv when available, falling back to the standard-library venv + pip when it isn't) and reports what actually happened, not a static prediction from metadata.

What it checks

  • Whether every requirement resolves at all (missing versions, yanked releases, platform/ABI mismatches — reported with the resolver's own explanation)
  • Whether requirements in the same source conflict with each other

What it deliberately does not check (yet)

  • BLAS/LAPACK backend compatibility — this is a post-install diagnostic (numpy.show_config()), not something knowable before installing
  • GPU/CUDA driver compatibility beyond what the resolver itself reports — PyTorch-style packages that ship on a separate index aren't covered
  • setup.py-only packages with no pyproject.toml/requirements.txt/ setup.cfg (would require unsafe code execution to parse reliably)

Install

Not yet published to PyPI — install directly from the repo:

uv tool install git+https://github.com/jahyunlee00299/compat-check

(or pipx install git+https://github.com/jahyunlee00299/compat-check, or clone and pip install . into a venv)

Usage

compat-check <github-url-or-pypi-package-name> [--python 3.11] [--no-cache] [--tree]

The GitHub source accepts the forms people actually paste:

https://github.com/owner/repo
https://github.com/owner/repo.git
https://github.com/owner/repo/tree/some-branch
https://github.com/owner/repo/blob/some-branch/setup.py
github.com/owner/repo
git@github.com:owner/repo.git

Without an explicit branch, the repository's real default branch is looked up rather than guessed, so a repo defaulting to something other than main costs no wasted requests. If the GitHub API is unreachable or its unauthenticated budget (60 requests/hour) is exhausted, the lookup degrades to trying main then master — it is an optimization, not a requirement.

Exit codes: 0 clean, 1 conflicts found, 2 source could not be resolved at all (bad URL, nonexistent package), 3 invalid parameter (checked before any network call, so a typo costs nothing).

A repository that cannot be read is reported as what it is: a missing or private repo (GitHub returns the same 404 for both, so no tool can tell them apart), an exhausted API budget with its reset time, or a GitHub reference that could not be parsed — never as a missing PyPI package.

--python and the pip fallback

--python is honoured only by the uv backend. The pip fallback builds its venv with the standard-library venv module, which can only clone the interpreter compat-check is itself running on — it cannot fetch another version. Rather than accept the flag and quietly ignore it, the report states the version actually probed:

$ compat-check requests --python 3.9     # on a machine without uv
compat-check: requests
backend: pip
python: 3.13 (requested 3.9 — NOT honoured)

with the reason on stderr. The cache is keyed on the version that was really used, so two requests that run the identical probe share one cache entry instead of being stored under two versions, only one of which was measured. Install uv to target other Python versions for real.

--tree shows the full dependency tree (requires uv — no pip-backend equivalent exists):

$ compat-check https://github.com/pallets/flask --tree
...
https://github.com/pallets/flask
├── blinker v1.9.0
├── click v8.5.0
├── itsdangerous v2.2.0
├── jinja2 v3.1.6
│   └── markupsafe v3.0.3
├── markupsafe v3.0.3
└── werkzeug v3.1.8
    └── markupsafe v3.0.3

Results are cached locally (~/.cache/compat_check/, 7-day TTL) since a dry-run against the same environment and requirements won't change minute-to-minute. Use --no-cache to force a fresh probe. The cache holds the 500 most recent entries (oldest evicted first) and is invalidated automatically when compat-check's own version changes, so a resolver change never serves an answer computed by an older build.

How it works

  1. Fetch the requirement list — from pyproject.toml, requirements.txt, setup.cfg or setup.py on the GitHub repo, or from PyPI's JSON API for a bare package name.

    A setup.py is read by parsing it, never by running it: install_requires is resolved from the syntax tree, including the common REQUIRES = [...]; setup(install_requires=REQUIRES) form. When the value is computed at runtime (a function call, a concatenation, a comprehension) the answer is genuinely unknowable without executing a stranger's code, so compat-check says so instead of guessing. A requirements.txt that uses -r other.txt has those files fetched and spliced in, so the list is the complete one: on home-assistant/core that is 51 requirements rather than the 47 visible in the root file. An include that cannot be read is an error, never a silently shorter list.

    -c constraint files are read but kept separate — a constraint pins a package if something pulls it in, so treating those entries as requirements would inflate the same repo to 181 packages it never asked to install. They are then passed to the resolver as --constraint, the way pip and uv mean them: resolving requests under urllib3<1.0 really does yield requests==2.15.1 rather than the latest, so ignoring the file would check a different version set than the project installs.

    An editable install with extras (-e .[pg]) resolves those extras too — records' pg group genuinely requires psycopg2-binary. Where an extra cannot be read statically, the gap is reported rather than left silent.

  2. Create a disposable virtual environment.

  3. Run pip install --dry-run (or uv pip install --dry-run) against it — this resolves and would-download, but never actually installs anything or runs arbitrary setup code from the target package.

  4. Report the result, retrying with failing packages dropped one at a time so every conflict in a multi-package source gets surfaced, not just the first one the resolver hits.

License

MIT

Release files for compat-check 0.5.0

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

Source distribution (sdist)

Source distribution for compat-check 0.5.0
File Size Uploaded
compat_check-0.5.0.tar.gz 47.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for compat-check 0.5.0
File Interpreter ABI Platform
compat_check-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 83.3 kB

Release files / compat_check-0.5.0.tar.gz

Download URL compat_check-0.5.0.tar.gz
Size 47.8 kB
Tags Source
SHA-256 checksum
How to use checksums
8477912d5f68370c1d1f2154f55c57d20bbd6a1f104f50a256ac036b4e700c74
BLAKE2b-256 checksum
How to use checksums
4b2e91bdf40d8cfa095acb8c7c4d13bf930706eba4de75a9f1399dd4ac06400b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / compat_check-0.5.0-py3-none-any.whl

Download URL compat_check-0.5.0-py3-none-any.whl
Size 35.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d32a047dd7f2bf5668790f4324ef630e4926eaa94cc921634220d67327a82566
BLAKE2b-256 checksum
How to use checksums
9bd71f2a419f6bad3cd7e2a2686adbe628731dafc00b00554d993ac766abf79f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

0.6.0

2 release files

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

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