summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2020-01-11 10:37:28 -0500
committerGitHub <noreply@github.com>2020-01-11 10:37:28 -0500
commit136735c1a2efb320e4cbb64b40f1191228745b39 (patch)
tree476c18b19e6a0e61d4b130725f48be6ba4b0081b
parent5d978a2e73e9ad934bcd260ae0a0db5cd0ca27d0 (diff)
downloadcpython-git-136735c1a2efb320e4cbb64b40f1191228745b39.tar.gz
bpo-39297: Update for importlib_metadata 1.4. (GH-17947)
* 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>
-rw-r--r--Lib/importlib/metadata.py108
-rw-r--r--Misc/NEWS.d/next/Library/2020-01-11-01-15-37.bpo-39297.y98Z6Q.rst1
2 files changed, 73 insertions, 36 deletions
diff --git a/Lib/importlib/metadata.py b/Lib/importlib/metadata.py
index 53f9fb5934..ae8ecf9b85 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 0000000000..618f6f9f2b
--- /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).