Skip to content

Commit a246813

Browse files
committed
Add support for egg packages with files outside site-packages
1 parent 8635240 commit a246813

File tree

4 files changed

+58
-4
lines changed

4 files changed

+58
-4
lines changed

importlib_metadata/__init__.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -562,15 +562,31 @@ def _read_files_egginfo_installed(self):
562562
if not text or not subdir:
563563
return
564564

565+
site_path = self.locate_file('').resolve()
565566
paths = (
566-
(subdir / name)
567-
.resolve()
568-
.relative_to(self.locate_file('').resolve())
569-
.as_posix()
567+
self._relative_to(
568+
(subdir / name).resolve(),
569+
site_path,
570+
).as_posix()
570571
for name in text.splitlines()
571572
)
572573
return map('"{}"'.format, paths)
573574

575+
def _relative_to(self, path, root):
576+
"""
577+
Workaround for https://bugs.python.org/issue23082 where ".."
578+
isn't added by pathlib.Path.relative_to() when path is not
579+
a subpath of root.
580+
581+
One example of such a package is dask-labextension, which uses
582+
jupyter-packaging to install JupyterLab javascript files outside
583+
of site-packages.
584+
"""
585+
try:
586+
return path.relative_to(root)
587+
except ValueError:
588+
return pathlib.Path(os.path.relpath(path, root))
589+
574590
def _read_files_egginfo_sources(self):
575591
"""
576592
Read SOURCES.txt and return lines in a similar CSV-parsable

importlib_metadata/_meta.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,6 @@ def read_bytes(self) -> bytes:
7070

7171
def exists(self) -> bool:
7272
... # pragma: no cover
73+
74+
def resolve(self) -> bool:
75+
... # pragma: no cover

tests/fixtures.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,40 @@ def main():
252252
}
253253

254254

255+
class EggInfoPkgPipInstalledExternalDataFiles(OnSysPath, SiteBuilder):
256+
files: FilesSpec = {
257+
"egg_with_module_pkg.egg-info": {
258+
"PKG-INFO": "Name: egg_with_module-pkg",
259+
# SOURCES.txt is made from the source archive, and contains files
260+
# (setup.py) that are not present after installation.
261+
"SOURCES.txt": """
262+
egg_with_module.py
263+
setup.py
264+
egg_with_module.json
265+
egg_with_module_pkg.egg-info/PKG-INFO
266+
egg_with_module_pkg.egg-info/SOURCES.txt
267+
egg_with_module_pkg.egg-info/top_level.txt
268+
""",
269+
# installed-files.txt is written by pip, and is a strictly more
270+
# accurate source than SOURCES.txt as to the installed contents of
271+
# the package.
272+
"installed-files.txt": """
273+
../../../etc/jupyter/jupyter_notebook_config.d/egg_with_module.json
274+
../egg_with_module.py
275+
PKG-INFO
276+
SOURCES.txt
277+
top_level.txt
278+
""",
279+
# missing top_level.txt (to trigger fallback to installed-files.txt)
280+
},
281+
"egg_with_module.py": """
282+
def main():
283+
print("hello world")
284+
""",
285+
}
286+
287+
288+
255289
class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteBuilder):
256290
files: FilesSpec = {
257291
"egg_with_no_modules_pkg.egg-info": {

tests/test_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class APITests(
2929
fixtures.EggInfoPkg,
3030
fixtures.EggInfoPkgPipInstalledNoToplevel,
3131
fixtures.EggInfoPkgPipInstalledNoModules,
32+
fixtures.EggInfoPkgPipInstalledExternalDataFiles,
3233
fixtures.EggInfoPkgSourcesFallback,
3334
fixtures.DistInfoPkg,
3435
fixtures.DistInfoPkgWithDot,

0 commit comments

Comments
 (0)