Skip to main content

metapathology

Diagnose Python import hooks without changing import outcomes.

PyPI version Supported Python versions Tests Documentation Quality gate status

[!IMPORTANT] The documentation is AI generated with rough handholding, they will read like complete slop, sorry. It is accurate, but really annoying to read.

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, or sys.path (opt-in), with a stack trace; and
  • which modules were found without going through the usual sys.path search.

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 repeatable --report paths to export inferred formats from the same capture, for example --report diagnostic.txt --report diagnostic.json. 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.
  • --deep diagnostics: 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, use metapathology.monitoring() around the behavior of interest, then call write_report() 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 RuntimeWarning because it relies on CPython's import audit event.
  • Monitoring starts when metapathology does. Finders installed earlier by .pth files (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.

Release files for metapathology 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 metapathology 0.5.0
File Size Uploaded
metapathology-0.5.0.tar.gz 120.3 kB Details

Built distribution (wheel)

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

Total release size: 256.6 kB

Release files / metapathology-0.5.0.tar.gz

Download URL metapathology-0.5.0.tar.gz
Size 120.3 kB
Tags Source
SHA-256 checksum
How to use checksums
ae7b062a24574de1c8cbab4267fb3c2684e7673dde8de62ff8e8a6bc8e249902
BLAKE2b-256 checksum
How to use checksums
70be6c0833251b1d9ddf31599584725037a23d4b1c3d6e52cd0d13ef1e510c83
Upload date
Uploaded using Trusted Publishing?
What is 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}

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

Download URL metapathology-0.5.0-py3-none-any.whl
Size 136.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4435c98c59178d8c140f9c61158b6fce6a2bdf3bc0bcb4f8dd72d3e4ce89436a
BLAKE2b-256 checksum
How to use checksums
4f63fb3769e9b49c4dc2e1a1ae25ed66645a22a2b3552eca3c34cbde5e5249b3
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.2

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