GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-139899: Introduce MetaPathFinder.discover and PathEntryFinder.discover by FFY00 · Pull Request #139900 · python/cpython

Conversation

@FFY00

@FFY00 FFY00 commented Oct 10, 2025 •
edited by brettcannon
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member
  • Issue: Introduce MetaPathFinder.discover and PathEntryFinder.discover #139899

📚 Documentation preview 📚: https://cpython-previews--139900.org.readthedocs.build/

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@FFY00
FFY00 requested review from brettcannon, ericsnowcurrently, ncoghlan and warsaw as code owners October 10, 2025 13:09
@bedevere-app bedevere-app Bot mentioned this pull request Oct 10, 2025
Introduce MetaPathFinder.discover and PathEntryFinder.discover #139899
Closed
@bedevere-app bedevere-app Bot added the awaiting core review label Oct 10, 2025
@FFY00 FFY00 mentioned this pull request Oct 10, 2025
Add import suggestions for ModuleNotFoundError #134872
Closed
@FFY00

FFY00 commented Oct 10, 2025

Copy link
Copy Markdown
Member Author

Still needs tests, but I'll wait to see the feedback on the issue.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@FFY00
pythongh-139899: Introduce MetaPathFinder.discover and PathEntryFinde…
8099249
…r.discover

Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
FFY00 force-pushed the gh-139899 branch from 29215a0 to 8099249 Compare December 10, 2025 12:45
@FFY00 FFY00 mentioned this pull request Dec 10, 2025
GH-134872: add ModuleNotFoundError suggestions #142512
Merged
@FFY00

FFY00 commented Dec 10, 2025

Copy link
Copy Markdown
Member Author

I've updated the method to take a module spec, instead of a module object, as in some cases the parent might have failed to import.

In a follow-up, I will add a protocol for finders implementing .discover(), as @brettcannon suggested.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

FFY00 added 5 commits December 10, 2025 12:54
@FFY00
Fix doc reference
6816705
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Remove specific doc references
31d1a8f
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Fix docstrings
051cd1e
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Revert "Remove specific doc references"
c343d32
This reverts commit 31d1a8f.

Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Fix news references
a324d96
Signed-off-by: Filipe Laíns <[email protected]>
gpshead
gpshead reviewed Dec 13, 2025
View reviewed changes
Comment thread Lib/importlib/_bootstrap_external.py
if parent is None:
path = sys.path
else:
path = parent.submodule_search_locations

@gpshead gpshead Dec 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be None when parent is a non-package module? make a nicer error message or should this situation use sys.path?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@FFY00 FFY00 Dec 17, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can it be a non-package module if it has a child? It could be a namespace package, but that's still a package, and parent.submodule_search_locations should be an iterable objects when the spec is fully initialized, which it should always be at this point.

Unless I am missing something? Do we support package-like module extensions?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ncoghlan ncoghlan Jan 1, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @gpshead is referring to the error case where parent.submodule_search_locations is None because the caller supplied a "parent" spec from a non-package module. Instead of the default "'NoneType' object is not iterable" message from the failed iteration attempt, we should raise something more specific.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

FFY00 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/importlib/_bootstrap_external.py Outdated
return path_hook_for_FileFinder

def _find_children(self):
for entry in _os.scandir(self.path):

@gpshead gpshead Dec 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a with statement to explicitly close the iterator - https://docs.python.org/3/library/os.html#os.scandir.close

also consider handling exceptions similar to what _fill_cache does, which is also be needed around the is_dir and is_file bits within the loop below - https://docs.python.org/3/library/os.html#os.scandir.close

document exception handling semantics of discover. I doubt callers ever expect to need be prepared for those?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

FFY00 and ncoghlan reacted with thumbs up emoji
All reactions
  • 👍 2 reactions
Comment thread Lib/importlib/_bootstrap_external.py Outdated
return path_hook_for_FileFinder

def _find_children(self):
for entry in _os.scandir(self.path):

@gpshead gpshead Dec 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be cached or not? we should document the cache interaction behavior either way regardless.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@FFY00 FFY00 Dec 17, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so? Because it could change at runtime, no?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@FFY00 FFY00 Jan 20, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting back to this, I don't think we should touch anything in the cache if we keep using os.scandir. Alternatively, we can rewrite _find_children to use the cache / os.listdir.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/importlib/_bootstrap_external.py Outdated
# files
if entry.is_file():
yield from [
entry.name.removesuffix(suffix)

@gpshead gpshead Dec 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry.name could exist with multiple loader suffixes on the filesystem. dedupe to avoid redundant specs?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

FFY00 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Doc/library/importlib.rst
module spec. If *parent* is *None*, :meth:`MetaPathFinder.discover` will
search for top-level modules.

Returns an iterable of possible specs.

@gpshead gpshead Dec 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this be the first time we have a public API yielding things from importlib, do we actually want to do that or should this return a list?

callers consuming the results might be writing code that makes changes that could impact future results... yielding could get messy.

what are the intended use cases for the API? if it's something we expect callers to short circuit and stop iterating on after the first match maybe yield makes sense, but then we should probably just have an explicit direct discover_first API for that instead.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@FFY00 FFY00 Dec 17, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I considered.

The main use-case is finding a similar-named module to show as a hint on ModuleNotFoundError (eg. "Did you meant numpy?", when trying to import numby), so I think it would make sense to make this new API a generator, or at least some kind of lazy container.

For cases such as you describe, the user could just consume the full generator into a list to avoid any issue. Still leaving opportunity for the code that could leverage the benefit of this being a lazy API — scaning directories with a lot of files can take a while, not to mention the other exotic finders out there that may operate over the network or something like that.

I am not fundamentally opposed to make this method return a list, but I can't see the value in the trade-of if we document it properly.

callers consuming the results might be writing code that makes changes that could impact future results... yielding could get messy.

While this is technically possible, I would find it extremely uncommon. And I think it should be reasonable to assume that people who are knowledgeable enough to do that, would probably be aware of the downsides of making changes to the import machinery, while consuming the API

but then we should probably just have an explicit direct discover_first API for that instead

And what would that look like? Would it take a predicate function and return the first entry that matches?


So, would have a warning in the documentation regarding your concern be a good enough compromise?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ncoghlan ncoghlan Jan 1, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, for the "nearest matching module name" error message case, you just want to keep the closest match, so even though all the names need to be checked, you only need to keep a reference to one of them (the closest so far).

Since the potential number of results may be absurdly high in some situations, and the iterable form provides more options for consumers to handle that appropriately, I do think it makes sense to make it an iterable. The warning in the docs should explain why it's an iterable, since consumers that unconditionally convert the result to a list can run into problems regardless.

As far as prior examples of iterable import APIs goes, importlib.resources.contents was the first example I found in a quick look (covering a very similar situation, just for non-module resources rather than submodules)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

FFY00 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/importlib/_bootstrap_external.py Outdated
else:
path = parent.submodule_search_locations

for entry in path:

@gpshead gpshead Dec 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path can contain duplicate entries. should we dedupe?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@FFY00 FFY00 Dec 17, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sure 👍

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ncoghlan
ncoghlan reviewed Jan 1, 2026
View reviewed changes

@ncoghlan ncoghlan left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely looking promising, but I do agree with a couple of @gpshead's requested tidy ups.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/importlib/_bootstrap_external.py
if parent is None:
path = sys.path
else:
path = parent.submodule_search_locations

@ncoghlan ncoghlan Jan 1, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @gpshead is referring to the error case where parent.submodule_search_locations is None because the caller supplied a "parent" spec from a non-package module. Instead of the default "'NoneType' object is not iterable" message from the failed iteration attempt, we should raise something more specific.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

FFY00 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Misc/NEWS.d/next/Library/2025-10-10-14-08-58.gh-issue-139899.09leRY.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

FFY00 and others added 8 commits January 20, 2026 00:35
@FFY00
Add docs warning
dc6faa2
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Raise ValueError on invalid parent
3be757f
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Dedupe __path__ in PathFinder.discover
0da477f
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Use context manager and add error handling to os.scandir
282bef7
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Raise ValueError on invalid parent
469dc2a
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00
Dedupe when package exists with multiple suffixes
c09a12e
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00 @ncoghlan
Apply suggestions from code review
41cd071
Co-authored-by: Alyssa Coghlan <[email protected]>
@FFY00
Add tests
5488e93
Signed-off-by: Filipe Laíns <[email protected]>
@FFY00

FFY00 commented Feb 4, 2026

Copy link
Copy Markdown
Member Author

I believe I addressed the issues raised in the review, so if no-one is opposed, I'd like to merge this.

brettcannon reacted with hooray emoji
All reactions
  • 🎉 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@brettcannon
Merge branch 'main' into pythongh-139899
fc1de5f
@FFY00
FFY00 merged commit 157f271 into python:main Feb 19, 2026
47 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting core review label Feb 19, 2026
brijkapadia pushed a commit to brijkapadia/cpython that referenced this pull request Feb 28, 2026
@FFY00 @ncoghlan
@brettcannon @brijkapadia
pythongh-139899: Introduce MetaPathFinder.discover and PathEntryFinde…
d51318d
…r.discover (python#139900)

* pythongh-139899: Introduce MetaPathFinder.discover and PathEntryFinder.discover

Signed-off-by: Filipe Laíns <[email protected]>

* Fix doc reference

Signed-off-by: Filipe Laíns <[email protected]>

* Remove specific doc references

Signed-off-by: Filipe Laíns <[email protected]>

* Fix docstrings

Signed-off-by: Filipe Laíns <[email protected]>

* Revert "Remove specific doc references"

This reverts commit 31d1a8f.

Signed-off-by: Filipe Laíns <[email protected]>

* Fix news references

Signed-off-by: Filipe Laíns <[email protected]>

* Add docs warning

Signed-off-by: Filipe Laíns <[email protected]>

* Raise ValueError on invalid parent

Signed-off-by: Filipe Laíns <[email protected]>

* Dedupe __path__ in PathFinder.discover

Signed-off-by: Filipe Laíns <[email protected]>

* Use context manager and add error handling to os.scandir

Signed-off-by: Filipe Laíns <[email protected]>

* Raise ValueError on invalid parent

Signed-off-by: Filipe Laíns <[email protected]>

* Dedupe when package exists with multiple suffixes

Signed-off-by: Filipe Laíns <[email protected]>

* Apply suggestions from code review

Co-authored-by: Alyssa Coghlan <[email protected]>

* Add tests

Signed-off-by: Filipe Laíns <[email protected]>

---------

Signed-off-by: Filipe Laíns <[email protected]>
Co-authored-by: Alyssa Coghlan <[email protected]>
Co-authored-by: Brett Cannon <[email protected]>
ljfp pushed a commit to ljfp/cpython that referenced this pull request Apr 25, 2026
@FFY00 @ncoghlan
@brettcannon @ljfp
pythongh-139899: Introduce MetaPathFinder.discover and PathEntryFinde…
cb49153
…r.discover (python#139900)

* pythongh-139899: Introduce MetaPathFinder.discover and PathEntryFinder.discover

Signed-off-by: Filipe Laíns <[email protected]>

* Fix doc reference

Signed-off-by: Filipe Laíns <[email protected]>

* Remove specific doc references

Signed-off-by: Filipe Laíns <[email protected]>

* Fix docstrings

Signed-off-by: Filipe Laíns <[email protected]>

* Revert "Remove specific doc references"

This reverts commit 31d1a8f.

Signed-off-by: Filipe Laíns <[email protected]>

* Fix news references

Signed-off-by: Filipe Laíns <[email protected]>

* Add docs warning

Signed-off-by: Filipe Laíns <[email protected]>

* Raise ValueError on invalid parent

Signed-off-by: Filipe Laíns <[email protected]>

* Dedupe __path__ in PathFinder.discover

Signed-off-by: Filipe Laíns <[email protected]>

* Use context manager and add error handling to os.scandir

Signed-off-by: Filipe Laíns <[email protected]>

* Raise ValueError on invalid parent

Signed-off-by: Filipe Laíns <[email protected]>

* Dedupe when package exists with multiple suffixes

Signed-off-by: Filipe Laíns <[email protected]>

* Apply suggestions from code review

Co-authored-by: Alyssa Coghlan <[email protected]>

* Add tests

Signed-off-by: Filipe Laíns <[email protected]>

---------

Signed-off-by: Filipe Laíns <[email protected]>
Co-authored-by: Alyssa Coghlan <[email protected]>
Co-authored-by: Brett Cannon <[email protected]>
This was referenced Aug 17, 2026
FileFinder._find_children can loop forever on a persistently failing directory scan #155935
Open
gh-155935: Stop FileFinder._find_children retrying a failing directory scan #155939
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@gpshead gpshead gpshead left review comments
@ncoghlan ncoghlan ncoghlan left review comments
@brettcannon brettcannon Awaiting requested review from brettcannon brettcannon is a code owner
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner
@warsaw warsaw Awaiting requested review from warsaw warsaw is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@FFY00 @gpshead @ncoghlan @brettcannon
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.