Skip to main content

Diagnose which finders handle imports and how sys.meta_path changes

Project description

metapathology

Diagnose Python import hooks without changing import outcomes.

PyPI version Supported Python versions Tests Documentation Quality gate status

[!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, 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 --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.
  • --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, 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 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.

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

metapathology-0.4.4.tar.gz (99.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

metapathology-0.4.4-py3-none-any.whl (106.4 kB view details)

Uploaded Python 3

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

Hashes for metapathology-0.4.4.tar.gz
Algorithm Hash digest
SHA256 8e9669b0056629194e8357ab1b01b99a7fa05bd3639abac14ae6d804dd680ac9
MD5 1a6f7d6b1a155db80e9b2e80ad8e7967
BLAKE2b-256 de2fa924fdf22e79c559cc4baeef561658d92225c82cbdb9c0dff81efb61fc7a

See more details on using hashes here.

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

Hashes for metapathology-0.4.4-py3-none-any.whl
Algorithm Hash digest
SHA256 4058ea718756961ca00f05b27ae6bce5cabab7df44ff45937bc21ddf48da1456
MD5 5edce5169bfeba2353952aa12e7b841f
BLAKE2b-256 5c05e219e98b50fb08b028fc39863d7e1e767d78e418e531590b269d8039cfec

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page