From 61b0f297960d678d260f31319e7d53584d901e36 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Fri, 10 Mar 2023 14:45:19 +0100 Subject: [PATCH] Distribution.files: Prefer *.egg-info/installed-files.txt to SOURCES.txt When listing the files in a *.egg-info distribution, prefer using *.egg-info/installed-files.txt instead of *.egg-info/SOURCES.txt. installed-files.txt is written by pip[1] when installing a package, whereas the SOURCES.txt is written by setuptools when creating a source archive[2]. installed-files.txt is only present when the package has been installed by pip, so we cannot depend on it always being available. However, when it _is_ available, it is an accurate record of what files are installed. SOURCES.txt, on the other hand, is always avaiable, but is not always accurate: Since it is generated from the source archive, it will often include files (like 'setup.py') that are no longer available after the package has been installed. Fixes #115 for the cases where a installed-files.txt file is available. [1]: https://pip.pypa.io/en/stable/news/#v0-3 [2]: https://setuptools.pypa.io/en/latest/deprecated/python_eggs.html#sources-txt-source-files-manifest --- importlib_metadata/__init__.py | 37 ++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 9a36a8e6..3b0d8247 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -460,8 +460,8 @@ def files(self): :return: List of PackagePath for this distribution or None Result is `None` if the metadata file that enumerates files - (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is - missing. + (i.e. RECORD for dist-info, or installed-files.txt or + SOURCES.txt for egg-info) is missing. Result may be empty if the metadata exists but is empty. """ @@ -476,7 +476,11 @@ def make_file(name, hash=None, size_str=None): def make_files(lines): return list(starmap(make_file, csv.reader(lines))) - return make_files(self._read_files_distinfo() or self._read_files_egginfo()) + return make_files( + self._read_files_distinfo() + or self._read_files_egginfo_installed() + or self._read_files_egginfo_sources() + ) def _read_files_distinfo(self): """ @@ -485,10 +489,35 @@ def _read_files_distinfo(self): text = self.read_text('RECORD') return text and text.splitlines() - def _read_files_egginfo(self): + def _read_files_egginfo_installed(self): + """ + installed-files.txt might contain literal commas, so wrap + each line in quotes. Also, the entries in installed-files.txt + are relative to the .egg-info/ subdir (not relative to the + parent site-packages directory that make_file() expects). + + This file is written when the package is installed by pip, + but it might not be written for other installation methods. + Hence, even if we can assume that this file is accurate + when it exists, we cannot assume that it always exists. + """ + text = self.read_text('installed-files.txt') + # We need to prepend the .egg-info/ subdir to the lines in this file. + # But this subdir is only available in the PathDistribution's self._path + # which is not easily accessible from this base class... + subdir = getattr(self, '_path', None) + return text and subdir and [f'"{subdir}/{line}"' for line in text.splitlines()] + + def _read_files_egginfo_sources(self): """ SOURCES.txt might contain literal commas, so wrap each line in quotes. + + Note that SOURCES.txt is not a reliable source for what + files are installed by a package. This file is generated + for a source archive, and the files that are present + there (e.g. setup.py) may not correctly reflect the files + that are present after the package has been installed. """ text = self.read_text('SOURCES.txt') return text and map('"{}"'.format, text.splitlines())