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

Add better support for packages with files outside site-packages #482

Merged
merged 12 commits into from
Jun 23, 2024
7 changes: 3 additions & 4 deletions importlib_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import collections

from . import _meta
from .compat import py39
from .compat import py39, py311
from ._collections import FreezableDefaultDict, Pair
from ._compat import (
NullFinder,
Expand Down Expand Up @@ -570,9 +570,8 @@ def _read_files_egginfo_installed(self):
return

paths = (
(subdir / name)
.resolve()
.relative_to(self.locate_file('').resolve())
py311.relative_fix((subdir / name).resolve())
.relative_to(self.locate_file('').resolve(), walk_up=True)
.as_posix()
for name in text.splitlines()
)
Expand Down
22 changes: 22 additions & 0 deletions importlib_metadata/compat/py311.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import os
import pathlib
import sys
import types


def wrap(path): # pragma: no cover
"""
Workaround for https://github.com/python/cpython/issues/84538
to add backward compatibility for walk_up=True.
An example affected package is dask-labextension, which uses
jupyter-packaging to install JupyterLab javascript files outside
of site-packages.
"""

def relative_to(root, *, walk_up=False):
return pathlib.Path(os.path.relpath(path, root))

return types.SimpleNamespace(relative_to=relative_to)


relative_fix = wrap if sys.version_info < (3, 12) else lambda x: x
1 change: 1 addition & 0 deletions newsfragments/455.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When reading installed files from an egg, use ``relative_to(walk_up=True)`` to honor files installed outside of the installation root.
34 changes: 34 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,40 @@ def main():
}


class EggInfoPkgPipInstalledExternalDataFiles(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egg_with_module_pkg.egg-info": {
"PKG-INFO": "Name: egg_with_module-pkg",
# SOURCES.txt is made from the source archive, and contains files
# (setup.py) that are not present after installation.
"SOURCES.txt": """
egg_with_module.py
setup.py
egg_with_module.json
egg_with_module_pkg.egg-info/PKG-INFO
egg_with_module_pkg.egg-info/SOURCES.txt
egg_with_module_pkg.egg-info/top_level.txt
""",
# installed-files.txt is written by pip, and is a strictly more
# accurate source than SOURCES.txt as to the installed contents of
# the package.
"installed-files.txt": """
../../../etc/jupyter/jupyter_notebook_config.d/relative.json
/etc/jupyter/jupyter_notebook_config.d/absolute.json
../egg_with_module.py
PKG-INFO
SOURCES.txt
top_level.txt
""",
# missing top_level.txt (to trigger fallback to installed-files.txt)
},
"egg_with_module.py": """
def main():
print("hello world")
""",
}


class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egg_with_no_modules_pkg.egg-info": {
Expand Down
1 change: 1 addition & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class APITests(
fixtures.EggInfoPkg,
fixtures.EggInfoPkgPipInstalledNoToplevel,
fixtures.EggInfoPkgPipInstalledNoModules,
fixtures.EggInfoPkgPipInstalledExternalDataFiles,
fixtures.EggInfoPkgSourcesFallback,
fixtures.DistInfoPkg,
fixtures.DistInfoPkgWithDot,
Expand Down
Loading