Diagnose which finders handle imports and how sys.meta_path changes
Project description
metapathology
Diagnose Python import hooks without changing import outcomes.
[!IMPORTANT] This project is mostly AI generated (for now). I do understand what it does and how it works, but I only skimmed the code.
Slop status:
- Code is a bit slop but fine
- README is good
- the report is genuinely useful
- docs: usage, reading the report, limitations are good, others are slop
Some Python packages customize how imports work: pytest rewrites test
assertions, editable installs redirect imports to your source tree, beartype
instruments modules as they load. These customizations plug into
Python's import system
through sys.meta_path and sys.path_hooks — and when two of them are active
at once, one can silently prevent the other from ever seeing a module. The
symptom is usually confusing: a feature quietly does nothing, or a module
that clearly exists fails to import.
metapathology runs your program and reports what the import system actually
did:
- which finder located each imported module;
- where code changed
sys.meta_path,sys.path_hooks,sys.path_importer_cache, orsys.path(opt-in), with a stack trace; and - which modules were found without going through the usual
sys.pathsearch.
It only observes. It never loads a module, returns a spec, or changes which finder wins, and everything it installs is removed on exit (except a CPython audit hook, which becomes a no-op because Python cannot unregister it).
Full documentation: glinte.github.io/metapathology.
Usage
Requires CPython 3.10+. No runtime dependencies — it works even in an environment where other packages fail to import.
$ pip install metapathology
$ python -m metapathology myscript.py --my-args
$ python -m metapathology -m pytest tests/
$ python -m metapathology # monitored interactive interpreter
Your program runs normally; the report is printed to standard error when it
exits. Use --report diagnostic.json to write a JSON file instead. Prefer
python -m metapathology over the metapathology command so the tool runs in
the same interpreter and virtual environment as your program.
If you cannot wrap the program (a notebook, an embedded interpreter, a
conftest.py), call the library API:
import metapathology
metapathology.install() # as early as possible
See Using metapathology for all CLI options, report files, environment variables, and lifecycle control.
Reading the report
A real example: beartype#556,
where beartype.claw silently did nothing in a scikit-build-core editable
install. The report shows why in its first comparison — the editable-install
finder found the module first, so beartype's path hook never saw it:
== metapathology report ==
target outcome: completed (exit status 0)
verdict: no problems found; some modules were found by a custom finder instead of the standard path search — listed below for review
...
-- modules found by a custom finder (1) --
'myproject':
during the run: ScikitBuildRedirectingFinder, loader _ScikitBuildLoaderWrapper, origin 'src\myproject\__init__.py'
standard search at report time: PathFinder, loader BeartypeSourceFileLoader, same origin
differences: loader type
note: this finder ran before PathFinder, so the standard path search never saw the module
-- finder calls --
ScikitBuildRedirectingFinder: called 365 times, found 1 module
myproject
The report leads with a verdict, then numbered findings when something looks
wrong, then the supporting evidence: comparisons like the one above, finder
call counts, an event timeline, and stack traces for every sys.meta_path
and sys.path_hooks change. Deep reports also correlate failed descendant
imports with a regular module that displaced an earlier namespace candidate,
and later failures with an earlier successful load from the same loader and
origin. Reading the report
explains every section and finding category.
Overhead
Rough figures from the benchmarks (GitHub-hosted runners, CPython 3.10 and 3.14):
- Default monitoring: imports take roughly 1.1–1.7× as long (median ~1.1× when only standard finders run, ~1.35× with a custom finder), and each import retains roughly 0.3–1 KB of memory for the report.
--deepdiagnostics: imports take roughly 7–15× as long and retain ~4 KB each. Use deep mode only in a controlled reproduction.
Every event is kept until the report is written, so memory grows with import
activity. For a long-running process, install just before the behavior of
interest and call write_report() and uninstall() once it is captured.
How it works
metapathology observes imports through several cooperating mechanisms: a
sys.addaudithook()
callback records each import as it starts, sys.meta_path and
sys.path_hooks are temporarily replaced with list subclasses that record
every change with a stack trace, each finder's find_spec() method is wrapped
to record whether it found the module, and sys.path_importer_cache is
snapshotted at key points. At report time, modules found by a custom finder
are compared with a fresh PathFinder search to reveal bypasses.
Use --sys-path-monitoring when path ordering itself is suspect. It is
opt-in because replacing the general-purpose sys.path list has a wider
compatibility surface; --deep enables it with the other invasive observers.
How it works walks through Python's import machinery and exactly what each mechanism can and cannot see.
Caveats
- CPython only. Monitoring on other implementations emits a
RuntimeWarningbecause it relies on CPython'simportaudit event. - Monitoring starts when metapathology does. Finders installed earlier by
.pthfiles (this is how scikit-build-core's finder arrives) appear in the initial snapshot without a stack trace. An optional bootstrap can start monitoring during interpreter startup to catch some of them. - This is a debugging tool. It temporarily modifies
sys.meta_path,sys.path_hooks, and, when requested,sys.path; do not leave it enabled in production. - Reports contain paths, command lines, and stack file names — review before sharing.
See Limitations for the complete list of observation boundaries. For frozen executables (PyInstaller, Nuitka, cx_Freeze) and embedded interpreters, see the frozen application guide.
Project details
Release history Release notifications | RSS feed
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 metapathology-0.4.4.tar.gz.
File metadata
- Download URL: metapathology-0.4.4.tar.gz
- Upload date:
- Size: 99.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e9669b0056629194e8357ab1b01b99a7fa05bd3639abac14ae6d804dd680ac9
|
|
| MD5 |
1a6f7d6b1a155db80e9b2e80ad8e7967
|
|
| BLAKE2b-256 |
de2fa924fdf22e79c559cc4baeef561658d92225c82cbdb9c0dff81efb61fc7a
|
File details
Details for the file metapathology-0.4.4-py3-none-any.whl.
File metadata
- Download URL: metapathology-0.4.4-py3-none-any.whl
- Upload date:
- Size: 106.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4058ea718756961ca00f05b27ae6bce5cabab7df44ff45937bc21ddf48da1456
|
|
| MD5 |
5edce5169bfeba2353952aa12e7b841f
|
|
| BLAKE2b-256 |
5c05e219e98b50fb08b028fc39863d7e1e767d78e418e531590b269d8039cfec
|