From 136735c1a2efb320e4cbb64b40f1191228745b39 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Jan 2020 10:37:28 -0500 Subject: [PATCH] bpo-39297: Update for importlib_metadata 1.4. (GH-17947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bpo-39297: Update for importlib_metadata 1.4. Includes performance updates. * 📜🤖 Added by blurb_it. * Update blurb Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> --- Lib/importlib/metadata.py | 108 ++++++++++++------ .../2020-01-11-01-15-37.bpo-39297.y98Z6Q.rst | 1 + 2 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-01-11-01-15-37.bpo-39297.y98Z6Q.rst diff --git a/Lib/importlib/metadata.py b/Lib/importlib/metadata.py index 53f9fb59346..ae8ecf9b850 100644 --- a/Lib/importlib/metadata.py +++ b/Lib/importlib/metadata.py @@ -10,6 +10,7 @@ import zipfile import operator import functools import itertools +import posixpath import collections from configparser import ConfigParser @@ -371,10 +372,6 @@ class DistributionFinder(MetaPathFinder): """ return vars(self).get('path', sys.path) - @property - def pattern(self): - return '.*' if self.name is None else re.escape(self.name) - @abc.abstractmethod def find_distributions(self, context=Context()): """ @@ -386,6 +383,73 @@ class DistributionFinder(MetaPathFinder): """ +class FastPath: + """ + Micro-optimized class for searching a path for + children. + """ + + def __init__(self, root): + self.root = root + + def joinpath(self, child): + return pathlib.Path(self.root, child) + + def children(self): + with suppress(Exception): + return os.listdir(self.root or '') + with suppress(Exception): + return self.zip_children() + return [] + + def zip_children(self): + zip_path = zipfile.Path(self.root) + names = zip_path.root.namelist() + self.joinpath = zip_path.joinpath + + return ( + posixpath.split(child)[0] + for child in names + ) + + def is_egg(self, search): + root_n_low = os.path.split(self.root)[1].lower() + + return ( + root_n_low == search.normalized + '.egg' + or root_n_low.startswith(search.prefix) + and root_n_low.endswith('.egg')) + + def search(self, name): + for child in self.children(): + n_low = child.lower() + if (n_low in name.exact_matches + or n_low.startswith(name.prefix) + and n_low.endswith(name.suffixes) + # legacy case: + or self.is_egg(name) and n_low == 'egg-info'): + yield self.joinpath(child) + + +class Prepared: + """ + A prepared search for metadata on a possibly-named package. + """ + normalized = '' + prefix = '' + suffixes = '.dist-info', '.egg-info' + exact_matches = [''][:0] + + def __init__(self, name): + self.name = name + if name is None: + return + self.normalized = name.lower().replace('-', '_') + self.prefix = self.normalized + '-' + self.exact_matches = [ + self.normalized + suffix for suffix in self.suffixes] + + class MetadataPathFinder(DistributionFinder): @classmethod def find_distributions(cls, context=DistributionFinder.Context()): @@ -397,45 +461,17 @@ class MetadataPathFinder(DistributionFinder): (or all names if ``None`` indicated) along the paths in the list of directories ``context.path``. """ - found = cls._search_paths(context.pattern, context.path) + found = cls._search_paths(context.name, context.path) return map(PathDistribution, found) @classmethod - def _search_paths(cls, pattern, paths): + def _search_paths(cls, name, paths): """Find metadata directories in paths heuristically.""" return itertools.chain.from_iterable( - cls._search_path(path, pattern) - for path in map(cls._switch_path, paths) + path.search(Prepared(name)) + for path in map(FastPath, paths) ) - @staticmethod - def _switch_path(path): - PYPY_OPEN_BUG = False - if not PYPY_OPEN_BUG or os.path.isfile(path): # pragma: no branch - with suppress(Exception): - return zipfile.Path(path) - return pathlib.Path(path) - - @classmethod - def _matches_info(cls, normalized, item): - template = r'{pattern}(-.*)?\.(dist|egg)-info' - manifest = template.format(pattern=normalized) - return re.match(manifest, item.name, flags=re.IGNORECASE) - - @classmethod - def _matches_legacy(cls, normalized, item): - template = r'{pattern}-.*\.egg[\\/]EGG-INFO' - manifest = template.format(pattern=normalized) - return re.search(manifest, str(item), flags=re.IGNORECASE) - - @classmethod - def _search_path(cls, root, pattern): - if not root.is_dir(): - return () - normalized = pattern.replace('-', '_') - return (item for item in root.iterdir() - if cls._matches_info(normalized, item) - or cls._matches_legacy(normalized, item)) class PathDistribution(Distribution): diff --git a/Misc/NEWS.d/next/Library/2020-01-11-01-15-37.bpo-39297.y98Z6Q.rst b/Misc/NEWS.d/next/Library/2020-01-11-01-15-37.bpo-39297.y98Z6Q.rst new file mode 100644 index 00000000000..618f6f9f2b7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-11-01-15-37.bpo-39297.y98Z6Q.rst @@ -0,0 +1 @@ +Improved performance of importlib.metadata distribution discovery and resilients to inaccessible sys.path entries (importlib_metadata v1.4.0).