Skip to content

Install the finder only after the module is initialized - #556

Open
jaraco wants to merge 1 commit into
mainfrom
bugfix/525-install-after-init
Open

jaraco wants to merge 1 commit into
mainfrom
bugfix/525-install-after-init

Conversation

@jaraco

@jaraco jaraco commented Oct 31, 2026

Copy link
Copy Markdown
Member

install appended a MetadataPathFinder to sys.meta_path while the module body was still executing, 36 lines before PathDistribution is bound. PathDistribution is the only global the installed finder has yet to see — Prepared, FastPath and DistributionFinder all precede it — which is why it is specifically this name that fails.

Anything walking sys.meta_path in that window resolves the finder against incomplete globals:

  File ".../importlib/metadata/__init__.py", line 486, in <genexpr>
    resolver(context) for resolver in cls._discover_resolvers()
  File ".../importlib_metadata/__init__.py", line 1054, in find_distributions
    return map(PathDistribution, found)
NameError: name 'PathDistribution' is not defined

Two conditions reach it:

  • Another thread calling entry_points(). No module lock applies, because that thread imports nothing — it only iterates sys.meta_path.
  • An import aborted in the window, as an async exception (a signal handler, a soft time limit, KeyboardInterrupt) can do. The module is dropped from sys.modules but the half-built finder stays installed for the life of the process, install has already deleted PathFinder.find_distributions so the orphan is the only provider, and even a later successful import does not displace it.

The second explains why #525 wasn't reproducible: the import that fails is not the import that reports, and consumers of a backport conventionally swallow its ImportError.

Installing at the end of the module, rather than just after PathDistribution, states the invariant the finder actually depends on, so a later reordering can't silently reopen the window.

The stdlib copy is unaffected — install is stripped there and PathFinder.find_distributions imports MetadataPathFinder lazily — so this needs no CPython-side change and stays clear of python/cpython#160387.

Test

FinderInstallTests snapshots the module globals at the moment the finder is published and asserts PathDistribution is already bound. Without the fix:

E       AssertionError: assert 'PathDistribution' in {'Any', 'Callable', 'Distribution', 'DistributionFinder', 'EntryPoint', 'EntryPoints', ...}

Closes #525.

🤖 Generated with Claude Code

`install` appended a `MetadataPathFinder` to `sys.meta_path` while the
module body was still executing, 36 lines before `PathDistribution` is
bound, and `PathDistribution` is the only global the installed finder has
yet to see (`Prepared`, `FastPath` and `DistributionFinder` all precede
it). Anything walking `sys.meta_path` in that window resolves it against
incomplete globals and raises `NameError: name 'PathDistribution' is not
defined`.

Two conditions reach the window. Another thread calling `entry_points()`
takes no module lock, as it imports nothing and only iterates
`sys.meta_path`. And an import aborted in the window, as an async
exception such as a signal handler or a soft time limit can do, leaves
the half-built finder installed for the life of the process: the module
is dropped from `sys.modules`, `install` has already deleted
`PathFinder.find_distributions` so the orphan is the only provider, and
even a later successful import does not displace it.

Installing at the end of the module, rather than just after
`PathDistribution`, states the invariant the finder depends on and so
survives any later reordering.

The stdlib copy is unaffected, where `install` is stripped and
`PathFinder.find_distributions` imports `MetadataPathFinder` lazily.

Closes #525.

Co-Authored-By: Claude Opus 5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NameError: name 'PathDistribution' is not defined

1 participant