Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce local versions of objects #505

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion importlib_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
from ._compat import (
NullFinder,
install,
localize,
)
from ._functools import method_cache, pass_none
from ._functools import apply, compose, method_cache, pass_none
from ._itertools import always_iterable, bucket, unique_everseen
from ._meta import PackageMetadata, SimplePath
from .compat import py39, py311
Expand Down Expand Up @@ -409,6 +410,7 @@ def locate_file(self, path: str | os.PathLike[str]) -> SimplePath:
"""

@classmethod
@apply(localize.dist)
def from_name(cls, name: str) -> Distribution:
"""Return the Distribution for the given package name.

Expand All @@ -427,6 +429,7 @@ def from_name(cls, name: str) -> Distribution:
raise PackageNotFoundError(name)

@classmethod
@apply(functools.partial(map, localize.dist))
def discover(
cls, *, context: Optional[DistributionFinder.Context] = None, **kwargs
) -> Iterable[Distribution]:
Expand Down Expand Up @@ -474,6 +477,7 @@ def _discover_resolvers():
return filter(None, declared)

@property
@apply(localize.message)
def metadata(self) -> _meta.PackageMetadata:
"""Return the parsed metadata for this Distribution.

Expand Down Expand Up @@ -524,6 +528,7 @@ def entry_points(self) -> EntryPoints:
return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)

@property
@apply(pass_none(compose(list, functools.partial(map, localize.package_path))))
def files(self) -> Optional[List[PackagePath]]:
"""Files in this distribution.

Expand Down
File renamed without changes.
45 changes: 45 additions & 0 deletions importlib_metadata/_compat/localize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from __future__ import annotations

import email.message
import importlib.metadata
import warnings
from typing import cast

import importlib_metadata._adapters


def dist(
dist: importlib_metadata.Distribution | importlib.metadata.Distribution,
) -> importlib_metadata.Distribution:
"""
Ensure dist is an :class:`importlib_metadata.Distribution`.
"""
if isinstance(dist, importlib_metadata.Distribution):
return dist
if isinstance(dist, importlib.metadata.PathDistribution):
return importlib_metadata.PathDistribution(
cast(importlib_metadata._meta.SimplePath, dist._path)
)
# workaround for when pytest has replaced importlib_metadata
# https://github.com/python/importlib_metadata/pull/505#issuecomment-2344329001
if dist.__class__.__module__ != 'importlib_metadata':
warnings.warn(f"Unrecognized distribution subclass {dist.__class__}")
return cast(importlib_metadata.Distribution, dist)


def message(
input: importlib_metadata._adapters.Message | email.message.Message,
) -> importlib_metadata._adapters.Message:
if isinstance(input, importlib_metadata._adapters.Message):
return input
return importlib_metadata._adapters.Message(input)


def package_path(
input: importlib_metadata.PackagePath | importlib.metadata.PackagePath,
) -> importlib_metadata.PackagePath:
if isinstance(input, importlib_metadata.PackagePath):
return input
replacement = importlib_metadata.PackagePath(input)
vars(replacement).update(vars(input))
return replacement
54 changes: 54 additions & 0 deletions importlib_metadata/_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,57 @@ def wrapper(param, *args, **kwargs):
return func(param, *args, **kwargs)

return wrapper


# From jaraco.functools 4.0.2
def compose(*funcs):
"""
Compose any number of unary functions into a single unary function.

Comparable to
`function composition <https://en.wikipedia.org/wiki/Function_composition>`_
in mathematics:

``h = g ∘ f`` implies ``h(x) = g(f(x))``.

In Python, ``h = compose(g, f)``.

>>> import textwrap
>>> expected = str.strip(textwrap.dedent(compose.__doc__))
>>> strip_and_dedent = compose(str.strip, textwrap.dedent)
>>> strip_and_dedent(compose.__doc__) == expected
True

Compose also allows the innermost function to take arbitrary arguments.

>>> round_three = lambda x: round(x, ndigits=3)
>>> f = compose(round_three, int.__truediv__)
>>> [f(3*x, x+1) for x in range(1,10)]
[1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
"""

def compose_two(f1, f2):
return lambda *args, **kwargs: f1(f2(*args, **kwargs))

return functools.reduce(compose_two, funcs)


def apply(transform):
"""
Decorate a function with a transform function that is
invoked on results returned from the decorated function.

>>> @apply(reversed)
... def get_numbers(start):
... "doc for get_numbers"
... return range(start, start+3)
>>> list(get_numbers(4))
[6, 5, 4]
>>> get_numbers.__doc__
'doc for get_numbers'
"""

def wrap(func):
return functools.wraps(func)(compose(transform, func))

return wrap
1 change: 1 addition & 0 deletions newsfragments/486.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When providers supply objects from ``importlib.metadata``, they are now adapted to the classes from ``importlib_metadata``.
17 changes: 17 additions & 0 deletions tests/compat/test_py39_compat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import contextlib
import pathlib
import sys
import unittest
import warnings

from importlib_metadata import (
distribution,
Expand Down Expand Up @@ -63,6 +65,9 @@ def test_compatibility_with_old_stdlib_path_distribution(self):
Ref python/importlib_metadata#396.
"""
self.fixtures.enter_context(fixtures.install_finder(self._meta_path_finder()))
self.fixtures.enter_context(
suppress_unrecognized_distribution_subclass_warning()
)

assert list(distributions())
assert distribution("distinfo_pkg")
Expand All @@ -72,3 +77,15 @@ def test_compatibility_with_old_stdlib_path_distribution(self):
assert list(metadata("distinfo_pkg"))
assert list(metadata("distinfo_pkg_custom"))
assert list(entry_points(group="entries"))


@contextlib.contextmanager
def suppress_unrecognized_distribution_subclass_warning():
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
category=UserWarning,
message="Unrecognized distribution subclass",
append=True,
)
yield
Loading