Skip to content

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

Merged
jaraco merged 4 commits into
mainfrom
bugfix/525-install-after-init
Nov 8, 2026
Merged

jaraco merged 4 commits 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 <noreply@anthropic.com>
Comment thread importlib_metadata/__init__.py Resolved
Comment thread tests/test_main.py Resolved
jaraco and others added 2 commits November 7, 2026 14:34
The comment explaining why the call sits at the end of the module was
four more lines for the CPython port to strip. `install` is itself
removed there, so its docstring carries the constraint for free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`install` has no counterpart in the stdlib, which reaches
`MetadataPathFinder` through its own `PathFinder` and publishes no finder
of its own, so the expectation belongs beside the other backport-specific
tests rather than among those test_main holds for both codebases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread importlib_metadata/_compat/__init__.py Resolved
Nothing consumes the return value now that the call is a statement, and
withholding it makes the decorator form bind None rather than quietly
restoring the window it was moved to close.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jaraco
jaraco merged commit 1d5a2ce into main Nov 8, 2026
34 of 36 checks passed
@jaraco
jaraco deleted the bugfix/525-install-after-init branch November 8, 2026 16:36
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