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 — Enforce local versions of objects and introduce shared protocols by jaraco · Pull Request #505 · python/importlib_metadata

Conversation

@jaraco

@jaraco jaraco commented Sep 11, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

If a provider is returning objects from importlib.metadata (Distribution, Message, PackagePath), replace them with local versions for predictable types for consumers and type checkers.

Closes #486; supersedes #487

  • Add a couple of functions from jaraco.functools.
  • Create 'localize_dist' function to convert stdlib to local versions of Distributions.
  • Create 'localize_metadata' function to normalize the outputs for .metadata.
  • Create 'localize_package_path' function to convert stdlib to local versions of PackagePath.

Sorry, something went wrong.

Uh oh!

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

All reactions
@jaraco
jaraco force-pushed the bugfix/486-local-objects branch from bd4ba0f to dba0127 Compare September 11, 2024 16:07
@jaraco

jaraco commented Sep 11, 2024

Copy link
Copy Markdown
Member Author

@abravalheri What do you think about this approach compared to #487?

The nice thing about this approach is it provides simpler type expectations for downstream consumers while also not imposing any restrictions on providers.

There's still a failing mypy check that I need to resolve, but the tests pass otherwise.

Also, it's a little bit ugly that objects that can't be converted are cast instead, which is technically incorrect, but should provide a low-disruption signal to address those objects. I'll flag that in a code comment.

All reactions

Sorry, something went wrong.

Uh oh!

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

jaraco
jaraco commented Sep 11, 2024
View reviewed changes
Comment thread importlib_metadata/_compat.py Outdated
Comment on lines +78 to +79
warnings.warn(f"Unrecognized distribution subclass {dist.__class__}")
return cast(importlib_metadata.Distribution, dist)

@jaraco jaraco Sep 11, 2024

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 block is perhaps the least respectable part of the proposed change. It means that any Distribution subclasses that aren't derived from importlib_metadata.Distribution or importlib.metadata.PathDistribution (e.g. a custom subclass of importlib.metadata.Distribution) will get a warning and then will possibly not comply with the interface. It's no worse than the status quo, however, except that there's no indication that a type may not be complying with an interface. At least now, a warning will be issued.

The proper fix will be for providers to supply importlib_metadata.Distribution subclasses when importlib_metadata is present.

Sorry, something went wrong.

Uh oh!

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

All reactions

@abravalheri abravalheri Sep 11, 2024

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.

Thank you very much for having a look on this. The solution looks good for me, although I share the feeling that the simple cast may be a little fragile.

The proper fix will be for providers to supply importlib_metadata.Distribution subclasses when importlib_metadata is present.

Should this be officially communicated somehow to the developers? I believe that there is some opposition against using a try..except if importlib_metadata is not included explicitly as a package dependency. We can see an example of that feeling in the thread starting with pypa/pyproject-hooks#195 (comment).

Sorry, something went wrong.

Uh oh!

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

All reactions

@abravalheri abravalheri Sep 11, 2024

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.

Not sure how the core devs would feel about adding this as a "tip" or recommendation in the importlib.metadata docs...

Sorry, something went wrong.

Uh oh!

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

All reactions

@jaraco jaraco Sep 11, 2024

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.

Should this be officially communicated somehow to the developers?

Yes, probably.

Not sure how the core devs would feel about adding this as a "tip" or recommendation in the importlib.metadata docs...

It should go wherever the docs are that provide guidance for providers. I'll figure out where that is and include it here or link it.

Sorry, something went wrong.

Uh oh!

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

All reactions

@jaraco jaraco Sep 11, 2024

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.

In python/cpython#123976, I've filed an issue upstream to track the documentation, but as I think more about what the documentation says, I'm not confident in the approach. I'll continue the conversation in #486.

Sorry, something went wrong.

Uh oh!

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

All reactions
@jaraco
jaraco force-pushed the bugfix/486-local-objects branch from 9504147 to 4b4b091 Compare September 11, 2024 16:52
@jaraco

jaraco commented Sep 11, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member Author

Tests are passing now on all but Python 3.8 and 3.9. They're failing on the project unit (pytest-checkdocs). It seems that the warning functionality is somehow getting triggered for pytest-checkdocs even though the class is of the intended type:

.::project
tests/data/sources/example2::project
  /home/runner/work/importlib_metadata/importlib_metadata/importlib_metadata/_compat.py:80: UserWarning: Unrecognized distribution subclass <class 'importlib_metadata.PathDistribution'>
    warnings.warn(f"Unrecognized distribution subclass {dist.__class__}")

Surely importlib_metadata.PathDistribution is an instance of importlib_metadata.Distribution. I need to figure out what's causing that not to be the case.

All reactions

Sorry, something went wrong.

Uh oh!

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

@jaraco

jaraco commented Sep 11, 2024

Copy link
Copy Markdown
Member Author

I think there are two issues going on:

First, when running under pytest, some modules (namely plugins) get imported early, before assertion rewriting is turned on. That includes pytest_checkdocs, which imports jaraco.packaging which imports build, which imports importlib_metadata. When later importlib_metadata is re-imported with the assert-rewrite support, a new copy is imported such that the types aren't comparable using isinstance:

> /Users/jaraco/code/python/importlib_metadata/importlib_metadata/_compat.py(82)localize_dist()
-> return cast(importlib_metadata.Distribution, dist)
(Pdb) importlib_metadata
<module 'importlib_metadata' from '/Users/jaraco/code/python/importlib_metadata/importlib_metadata/__init__.py'>
(Pdb) build._compat.importlib.metadata
<module 'importlib_metadata' from '/Users/jaraco/code/python/importlib_metadata/importlib_metadata/__init__.py'>
(Pdb) importlib_metadata is build._compat.importlib.metadata
False

A second, related issue, is that the same issue happens during localize_metadata. Because the isinstance check fails, the importlib_metadata._adapters.Message object gets re-constructed from an existing message (of the same class), causing the redent function to be invoked twice on the Description, mangling the reStructuredText (by adding ' '*8 at the beginning).

All reactions

Sorry, something went wrong.

Uh oh!

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

@jaraco
jaraco force-pushed the bugfix/486-local-objects branch from aa9c5ce to 9fae83b Compare September 11, 2024 18:25
@jaraco

jaraco commented Sep 11, 2024

Copy link
Copy Markdown
Member Author

I notice there is some small impact on performance:

exercises.py:cached distribution: 0:00:00.000302 (+0:00:00.000007, 2%)
exercises.py:discovery: 0:00:00.000300 (+0:00:00.000003, 1%)
exercises.py:entry_points(): 0:00:00.002260 (+0:00:00.000040, 2%)
exercises.py:entrypoint_regexp_perf: 0:00:00.000059 (+0:00:00.000001, 2%)
exercises.py:uncached distribution: 0:00:00.000452 (+0:00:00.000002, 0%)

Nothing too scary.

All reactions

Sorry, something went wrong.

Uh oh!

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

jaraco added 12 commits September 11, 2024 14:32
@jaraco
Add a couple of functions from jaraco.functools.
4261109
@jaraco
Create 'localize_dist' function to convert stdlib to local versions o…
e36808d
…f Distributions.
@jaraco
Create 'localize_metadata' function to normalize the outputs for .met…
6ff671d
…adata.
@jaraco
Create 'localize_package_path' function to convert stdlib to local ve…
5cd7d14
…rsions of PackagePath.
@jaraco
Add news fragment.
374bd98
@jaraco
In localize_dist, cast the result to paper over an error.
9000268
@jaraco
Stop trying to differentiate between importlib.metadata._adapters.Mes…
1a30e01
…sage and email.message.Message.
@jaraco
Suppress warnings when 'importlib_metadata' has been imported twice.
5a67414
@jaraco
Suppress warning when CustomDistribution, derived from importlib.meta…
5a67a0d
…data, is present.
@jaraco
Move _compat to its own package.
3dad754
@jaraco
Extract localize functions into their own module.
0ad266a
@jaraco
Rename 'localize.metadata' to 'localize.message'.
4a350a5
@jaraco
jaraco force-pushed the bugfix/486-local-objects branch from 9fae83b to 4a350a5 Compare September 11, 2024 18:32
@jaraco

jaraco commented Sep 11, 2024

Copy link
Copy Markdown
Member Author

I think there are two issues going on:

I confirmed that running pytest --assert plain against 1a30e01 (prior to the workarounds), the tests pass, confirming that the issue was indeed isolated to the pytest assert rewrite importer.

All reactions

Sorry, something went wrong.

Uh oh!

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

This was referenced Sep 11, 2024
Expand documentation for importlib.metadata custom providers to include caveats regarding backports python/cpython#123976
Open
API incompatibility with importlib.metadata (or at least the API is not type-safe?) #486
Closed
@jaraco jaraco mentioned this pull request Nov 22, 2025
Adopt "more sound" type annotations #487
Closed
jaraco added 4 commits December 26, 2025 11:31
@jaraco
Merge branch 'main' into bugfix/486-local-objects
3d464ad
@jaraco
Add some doctests to cover all the changes.
28c50a7
@jaraco
Implement interfaces for Distribution and PackagePath suitable for do…
1a14f99
…wnstream consumers.
@jaraco
🚡 Toil the docs.
95967ae
@jaraco jaraco changed the title Enforce local versions of objects Enforce local versions of objects and introduce shared protocols Dec 28, 2025
@jaraco
jaraco requested a review from abravalheri December 28, 2025 15:53
@jaraco
Add tests asserting that protocols match stdlib also.
c571e94
@jaraco
jaraco force-pushed the bugfix/486-local-objects branch from 5c844d9 to c571e94 Compare December 28, 2025 17:41
@jaraco
jaraco force-pushed the main branch from 7c0d919 to 0f88c48 Compare July 14, 2026 23:37
jaraco and others added 7 commits October 22, 2026 12:39
@jaraco
Merge remote-tracking branch 'origin/main' into bugfix/486-local-objects
fc381dd
# Conflicts:
#	importlib_metadata/__init__.py
#	tests/compat/test_py39_compat.py
@jaraco @claude
Adapt IDistribution to the metadata property that no longer returns N…
89d5dc8
…one.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Defer the expensive imports in localize.
da7125f
Importing importlib.metadata and _adapters eagerly regressed import time
by 17 msec (366%), largely defeating python/cpython#109829. Defer them to
the paths that need them, leaving a 4% cost for the module itself.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Describe the protocols for the consumers expected to rely on them.
bfcf9bd
Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Separate the stdlib conformance checks from the local ones.
13aa7e8
Asserting that importlib.metadata satisfies the protocols is a concern
of the backport alone, so it moves to the compat package, where it can
also account for Distribution.origin arriving in Python 3.13. What
remains asserts that the objects this package supplies conform.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Add tests for a provider supplying importlib.metadata objects.
de9f36d
Covers the scenario reported in #486, restoring the coverage lost when
the Python 3.9 compatibility tests were removed.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Drop the nitpick workaround, no longer needed, and add a missing newl…
b5cda44
…ine.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco jaraco mentioned this pull request Oct 22, 2026
gh-160386: Apply changes from importlib_metadata 10.0 and document the shared protocols python/cpython#160344
Open
@jaraco

jaraco commented Oct 22, 2026

Copy link
Copy Markdown
Member Author

A suggestion that came out of documenting this downstream (python/cpython#160344, for python/cpython#123976): consider warning on conformance rather than on class identity.

Today localize.dist warns for any Distribution subclass whose module isn't importlib_metadata:

if dist.__class__.__module__ != 'importlib_metadata':
    warnings.warn(f"Unrecognized distribution subclass {dist.__class__}")

Now that this PR adds IDistribution, that guard could ask whether the object actually satisfies the interface instead. The difference matters for the downstream guidance. As it stands, a provider that implements IDistribution completely still warns, and the person who sees the warning is not the person who can act on it. The custom-provider example in the CPython docs is exactly this case: a DatabaseDistribution that subclasses importlib.metadata.Distribution, implements read_text and locate_file, and inherits the rest. It works correctly under the backport and it warns. So the stdlib text has to explain a warning that providers cannot clear except by depending on this package, which is the coupling #486 set out to avoid. With a conformance check, the guidance collapses to "implement the whole interface", and the warning means something is genuinely missing.

One implementation note, since it is not obvious: isinstance(dist, IDistribution) is not the way to spell it. With requires-python = ">=3.11" and IDistribution carrying property members, isinstance against a runtime-checkable protocol evaluates those properties on 3.11, so the check would read METADATA for every distribution just to decide whether to warn, and a property that raises propagates out of isinstance. Measured on a protocol with metadata and version properties:

Python properties evaluated by isinstance property that raises
3.11 metadata, version exception propagates
3.13 none no effect

Checking the type avoids that on every supported version, and can name what is missing:

_REQUIRED = (
    'read_text', 'locate_file', 'metadata', 'name',
    'version', 'entry_points', 'files', 'requires',
)

if missing := [name for name in _REQUIRED if not hasattr(type(dist), name)]:
    warnings.warn(
        f"Distribution subclass {dist.__class__} is missing {', '.join(missing)}"
    )

(issubclass is not available here: protocols with non-method members reject it.) This would also subsume the __module__ check that currently works around pytest replacing importlib_metadata, since a replaced class still conforms.

Entirely possible the identity check is deliberate and I am missing a case it protects. The stdlib text works either way; it is just longer, and asks providers to live with a warning rather than to fix something.

All reactions

Sorry, something went wrong.

Uh oh!

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

jaraco and others added 7 commits October 22, 2026 14:30
@jaraco @claude
Leave 'origin' out of IDistribution.
af0007a
The stdlib only grew Distribution.origin in Python 3.13, so declaring it
makes the protocol describe something importlib.metadata does not supply
on 3.11 or 3.12, which in turn forced the conformance test to skip there.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Add a protocol conformance check that consults the type.
b28cfd3
Through Python 3.11, isinstance() against a runtime-checkable protocol
calls hasattr() on the instance, so asking whether a distribution
conforms would read its METADATA, and a property that raises would
escape the question. Python 3.12 resolved that in python/cpython#102433,
so defer to isinstance there.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Warn on conformance rather than on class identity.
619d534
A provider that implements the whole interface was warned about anyway
for deriving from importlib.metadata, and the person who saw the warning
was not the person who could act on it; clearing it meant depending on
this package, the coupling #486 set out to avoid. Ask instead what the
interface requires and the class omits, and name it.

This subsumes the __module__ check that worked around pytest replacing
importlib_metadata, since a replaced class still conforms.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Note the provider-facing half of the change.
3130095
Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Exempt from the conformance warning what the stdlib cannot supply.
c8c15a9
A provider cannot implement a member its importlib.metadata has no
version of, so faulting it for the absence would reintroduce the
warning nobody can clear. Collect those members in compat/py312, to
be deleted along with support for the Python that needs them.

The decision splits at 3.13 rather than 3.12 as a result, so the
type-only check moves here; plain isinstance still answers it wherever
nothing is exempt.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Restore 'origin' to IDistribution.
82f4d7c
Consumers should be able to rely on it, and the exemption now keeps a
provider on Python 3.11 or 3.12, whose stdlib has no origin to inherit,
from being warned about its absence.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco @claude
Mention the exemption in the release notes.
9e59038
Co-Authored-By: Claude Opus 5 <[email protected]>
jaraco added a commit to jaraco/cpython that referenced this pull request Oct 22, 2026
@jaraco @claude
pythongh-123976: Document the protocols shared with the backport
be9a5de
Assume the shared protocols from python/importlib_metadata#505 are
presented by this module as well: document IDistribution and
IPackagePath, correct PackageMetadata, which is already a protocol here
rather than an implementation of the backport's, and write the caveats
around the protocols as the interface both libraries satisfy.

Co-Authored-By: Claude Opus 5 <[email protected]>
@jaraco
jaraco merged commit e3dad22 into main Oct 23, 2026
34 of 36 checks passed

Uh oh!

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

@jaraco
jaraco deleted the bugfix/486-local-objects branch October 23, 2026 16:54
@jaraco jaraco mentioned this pull request Oct 31, 2026
Sync with importlib_metadata for Python 3.16 python/cpython#160386
Open
jaraco added a commit that referenced this pull request Nov 6, 2026
@jaraco @claude
Add a caveats page describing interoperation with importlib.metadata.
be2ea93
Documents the two class hierarchies, the protocols both satisfy, and
what that asks of consumers and of custom providers, following the
protocols and conformance-based warning added in #505. Moved here from
a draft against the stdlib docs (python/cpython#160344), where the
concern is largely this package's rather than the stdlib's.

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

Reviewers

@abravalheri abravalheri Awaiting requested review from abravalheri

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.

API incompatibility with importlib.metadata (or at least the API is not type-safe?)

Uh oh!

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

2 participants

@jaraco @abravalheri
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.