diff options
Diffstat (limited to 'third_party/python/setuptools/setuptools/package_index.py')
-rw-r--r-- | third_party/python/setuptools/setuptools/package_index.py | 357 |
1 files changed, 175 insertions, 182 deletions
diff --git a/third_party/python/setuptools/setuptools/package_index.py b/third_party/python/setuptools/setuptools/package_index.py index 3979b131b5..3130acef2c 100644 --- a/third_party/python/setuptools/setuptools/package_index.py +++ b/third_party/python/setuptools/setuptools/package_index.py @@ -1,4 +1,5 @@ -"""PyPI and direct package downloading""" +"""PyPI and direct package downloading.""" + import sys import os import re @@ -8,7 +9,6 @@ import socket import base64 import hashlib import itertools -import warnings import configparser import html import http.client @@ -19,15 +19,27 @@ from functools import wraps import setuptools from pkg_resources import ( - CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, - Environment, find_distributions, safe_name, safe_version, - to_filename, Requirement, DEVELOP_DIST, EGG_DIST, + CHECKOUT_DIST, + Distribution, + BINARY_DIST, + normalize_path, + SOURCE_DIST, + Environment, + find_distributions, + safe_name, + safe_version, + to_filename, + Requirement, + DEVELOP_DIST, + EGG_DIST, + parse_version, ) -from setuptools import ssl_support from distutils import log from distutils.errors import DistutilsError from fnmatch import translate from setuptools.wheel import Wheel +from setuptools.extern.more_itertools import unique_everseen + EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I) @@ -39,7 +51,9 @@ URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split() __all__ = [ - 'PackageIndex', 'distros_for_url', 'parse_bdist_wininst', + 'PackageIndex', + 'distros_for_url', + 'parse_bdist_wininst', 'interpret_distro_name', ] @@ -47,7 +61,8 @@ _SOCKET_TIMEOUT = 15 _tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" user_agent = _tmpl.format( - py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools) + py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools +) def parse_requirement_arg(spec): @@ -119,13 +134,15 @@ def distros_for_location(location, basename, metadata=None): wheel = Wheel(basename) if not wheel.is_compatible(): return [] - return [Distribution( - location=location, - project_name=wheel.project_name, - version=wheel.version, - # Increase priority over eggs. - precedence=EGG_DIST + 1, - )] + return [ + Distribution( + location=location, + project_name=wheel.project_name, + version=wheel.version, + # Increase priority over eggs. + precedence=EGG_DIST + 1, + ) + ] if basename.endswith('.exe'): win_base, py_ver, platform = parse_bdist_wininst(basename) if win_base is not None: @@ -136,7 +153,7 @@ def distros_for_location(location, basename, metadata=None): # for ext in EXTENSIONS: if basename.endswith(ext): - basename = basename[:-len(ext)] + basename = basename[: -len(ext)] return interpret_distro_name(location, basename, metadata) return [] # no extension matched @@ -149,57 +166,37 @@ def distros_for_filename(filename, metadata=None): def interpret_distro_name( - location, basename, metadata, py_version=None, precedence=SOURCE_DIST, - platform=None + location, basename, metadata, py_version=None, precedence=SOURCE_DIST, platform=None ): - """Generate alternative interpretations of a source distro name + """Generate the interpretation of a source distro name Note: if `location` is a filesystem filename, you should call ``pkg_resources.normalize_path()`` on it before passing it to this routine! """ - # Generate alternative interpretations of a source distro name - # Because some packages are ambiguous as to name/versions split - # e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc. - # So, we generate each possible interepretation (e.g. "adns, python-1.1.0" - # "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice, - # the spurious interpretations should be ignored, because in the event - # there's also an "adns" package, the spurious "python-1.1.0" version will - # compare lower than any numeric version number, and is therefore unlikely - # to match a request for it. It's still a potential problem, though, and - # in the long run PyPI and the distutils should go for "safe" names and - # versions in distribution archive names (sdist and bdist). parts = basename.split('-') if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]): # it is a bdist_dumb, not an sdist -- bail out return - for p in range(1, len(parts) + 1): - yield Distribution( - location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]), - py_version=py_version, precedence=precedence, - platform=platform - ) - - -# From Python 2.7 docs -def unique_everseen(iterable, key=None): - "List unique elements, preserving order. Remember all elements ever seen." - # unique_everseen('AAAABBBCCDAABBB') --> A B C D - # unique_everseen('ABBCcAD', str.lower) --> A B C D - seen = set() - seen_add = seen.add - if key is None: - for element in itertools.filterfalse(seen.__contains__, iterable): - seen_add(element) - yield element + # find the pivot (p) that splits the name from the version. + # infer the version as the first item that has a digit. + for p in range(len(parts)): + if parts[p][:1].isdigit(): + break else: - for element in iterable: - k = key(element) - if k not in seen: - seen_add(k) - yield element + p = len(parts) + + yield Distribution( + location, + metadata, + '-'.join(parts[:p]), + '-'.join(parts[p:]), + py_version=py_version, + precedence=precedence, + platform=platform + ) def unique_values(func): @@ -215,8 +212,10 @@ def unique_values(func): return wrapper -REL = re.compile(r"""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I) -# this line is here to fix emacs' cruddy broken syntax highlighting +REL = re.compile(r"""<([^>]*\srel\s{0,10}=\s{0,10}['"]?([^'" >]+)[^>]*)>""", re.I) +""" +Regex for an HTML tag with 'rel="val"' attributes. +""" @unique_values @@ -300,27 +299,33 @@ class PackageIndex(Environment): """A distribution index that scans web pages for download URLs""" def __init__( - self, index_url="https://pypi.org/simple/", hosts=('*',), - ca_bundle=None, verify_ssl=True, *args, **kw + self, + index_url="https://pypi.org/simple/", + hosts=('*',), + ca_bundle=None, + verify_ssl=True, + *args, + **kw ): - Environment.__init__(self, *args, **kw) - self.index_url = index_url + "/" [:not index_url.endswith('/')] + super().__init__(*args, **kw) + self.index_url = index_url + "/"[: not index_url.endswith('/')] self.scanned_urls = {} self.fetched_urls = {} self.package_pages = {} self.allows = re.compile('|'.join(map(translate, hosts))).match self.to_scan = [] - use_ssl = ( - verify_ssl - and ssl_support.is_available - and (ca_bundle or ssl_support.find_ca_bundle()) - ) - if use_ssl: - self.opener = ssl_support.opener_for(ca_bundle) - else: - self.opener = urllib.request.urlopen + self.opener = urllib.request.urlopen - def process_url(self, url, retrieve=False): + def add(self, dist): + # ignore invalid versions + try: + parse_version(dist.version) + except Exception: + return + return super().add(dist) + + # FIXME: 'PackageIndex.process_url' is too complex (14) + def process_url(self, url, retrieve=False): # noqa: C901 """Evaluate a URL as a possible download, and maybe retrieve it""" if url in self.scanned_urls and not retrieve: return @@ -396,7 +401,9 @@ class PackageIndex(Environment): return True msg = ( "\nNote: Bypassing %s (disallowed host; see " - "http://bit.ly/2hrImnY for details).\n") + "https://setuptools.pypa.io/en/latest/deprecated/" + "easy_install.html#restricting-downloads-with-allow-hosts for details).\n" + ) if fatal: raise DistutilsError(msg % url) else: @@ -428,62 +435,63 @@ class PackageIndex(Environment): dist.precedence = SOURCE_DIST self.add(dist) + def _scan(self, link): + # Process a URL to see if it's for a package page + NO_MATCH_SENTINEL = None, None + if not link.startswith(self.index_url): + return NO_MATCH_SENTINEL + + parts = list(map(urllib.parse.unquote, link[len(self.index_url) :].split('/'))) + if len(parts) != 2 or '#' in parts[1]: + return NO_MATCH_SENTINEL + + # it's a package page, sanitize and index it + pkg = safe_name(parts[0]) + ver = safe_version(parts[1]) + self.package_pages.setdefault(pkg.lower(), {})[link] = True + return to_filename(pkg), to_filename(ver) + def process_index(self, url, page): """Process the contents of a PyPI page""" - def scan(link): - # Process a URL to see if it's for a package page - if link.startswith(self.index_url): - parts = list(map( - urllib.parse.unquote, link[len(self.index_url):].split('/') - )) - if len(parts) == 2 and '#' not in parts[1]: - # it's a package page, sanitize and index it - pkg = safe_name(parts[0]) - ver = safe_version(parts[1]) - self.package_pages.setdefault(pkg.lower(), {})[link] = True - return to_filename(pkg), to_filename(ver) - return None, None - # process an index page into the package-page index for match in HREF.finditer(page): try: - scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) + self._scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) except ValueError: pass - pkg, ver = scan(url) # ensure this page is in the page index - if pkg: - # process individual package page - for new_url in find_external_links(url, page): - # Process the found URL - base, frag = egg_info_for_url(new_url) - if base.endswith('.py') and not frag: - if ver: - new_url += '#egg=%s-%s' % (pkg, ver) - else: - self.need_version_info(url) - self.scan_url(new_url) - - return PYPI_MD5.sub( - lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page - ) - else: + pkg, ver = self._scan(url) # ensure this page is in the page index + if not pkg: return "" # no sense double-scanning non-package pages + # process individual package page + for new_url in find_external_links(url, page): + # Process the found URL + base, frag = egg_info_for_url(new_url) + if base.endswith('.py') and not frag: + if ver: + new_url += '#egg=%s-%s' % (pkg, ver) + else: + self.need_version_info(url) + self.scan_url(new_url) + + return PYPI_MD5.sub( + lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page + ) + def need_version_info(self, url): self.scan_all( "Page at %s links to .py file(s) without version info; an index " - "scan is required.", url + "scan is required.", + url, ) def scan_all(self, msg=None, *args): if self.index_url not in self.fetched_urls: if msg: self.warn(msg, *args) - self.info( - "Scanning index of all packages (this may take a while)" - ) + self.info("Scanning index of all packages (this may take a while)") self.scan_url(self.index_url) def find_packages(self, requirement): @@ -514,9 +522,7 @@ class PackageIndex(Environment): """ checker is a ContentChecker """ - checker.report( - self.debug, - "Validating %%s checksum for %s" % filename) + checker.report(self.debug, "Validating %%s checksum for %s" % filename) if not checker.is_valid(): tfp.close() os.unlink(filename) @@ -553,7 +559,8 @@ class PackageIndex(Environment): else: # no distros seen for this name, might be misspelled meth, msg = ( self.warn, - "Couldn't find index page for %r (maybe misspelled?)") + "Couldn't find index page for %r (maybe misspelled?)", + ) meth(msg, requirement.unsafe_name) self.scan_all() @@ -591,9 +598,15 @@ class PackageIndex(Environment): spec = parse_requirement_arg(spec) return getattr(self.fetch_distribution(spec, tmpdir), 'location', None) - def fetch_distribution( - self, requirement, tmpdir, force_scan=False, source=False, - develop_ok=False, local_index=None): + def fetch_distribution( # noqa: C901 # is too complex (14) # FIXME + self, + requirement, + tmpdir, + force_scan=False, + source=False, + develop_ok=False, + local_index=None, + ): """Obtain a distribution suitable for fulfilling `requirement` `requirement` must be a ``pkg_resources.Requirement`` instance. @@ -625,15 +638,13 @@ class PackageIndex(Environment): if dist.precedence == DEVELOP_DIST and not develop_ok: if dist not in skipped: self.warn( - "Skipping development or system egg: %s", dist, + "Skipping development or system egg: %s", + dist, ) skipped[dist] = 1 continue - test = ( - dist in req - and (dist.precedence <= SOURCE_DIST or not source) - ) + test = dist in req and (dist.precedence <= SOURCE_DIST or not source) if test: loc = self.download(dist.location, tmpdir) dist.download_location = loc @@ -682,10 +693,15 @@ class PackageIndex(Environment): def gen_setup(self, filename, fragment, tmpdir): match = EGG_FRAGMENT.match(fragment) - dists = match and [ - d for d in - interpret_distro_name(filename, match.group(1), None) if d.version - ] or [] + dists = ( + match + and [ + d + for d in interpret_distro_name(filename, match.group(1), None) + if d.version + ] + or [] + ) if len(dists) == 1: # unambiguous ``#egg`` fragment basename = os.path.basename(filename) @@ -693,8 +709,7 @@ class PackageIndex(Environment): # Make sure the file has been downloaded to the temp dir. if os.path.dirname(filename) != tmpdir: dst = os.path.join(tmpdir, basename) - from setuptools.command.easy_install import samefile - if not samefile(filename, dst): + if not (os.path.exists(dst) and os.path.samefile(filename, dst)): shutil.copy2(filename, dst) filename = dst @@ -703,8 +718,9 @@ class PackageIndex(Environment): "from setuptools import setup\n" "setup(name=%r, version=%r, py_modules=[%r])\n" % ( - dists[0].project_name, dists[0].version, - os.path.splitext(basename)[0] + dists[0].project_name, + dists[0].version, + os.path.splitext(basename)[0], ) ) return filename @@ -762,7 +778,8 @@ class PackageIndex(Environment): def reporthook(self, url, filename, blocknum, blksize, size): pass # no-op - def open_url(self, url, warning=None): + # FIXME: + def open_url(self, url, warning=None): # noqa: C901 # is too complex (12) if url.startswith('file:'): return local_open(url) try: @@ -779,23 +796,22 @@ class PackageIndex(Environment): if warning: self.warn(warning, v.reason) else: - raise DistutilsError("Download error for %s: %s" - % (url, v.reason)) from v + raise DistutilsError( + "Download error for %s: %s" % (url, v.reason) + ) from v except http.client.BadStatusLine as v: if warning: self.warn(warning, v.line) else: raise DistutilsError( '%s returned a bad status line. The server might be ' - 'down, %s' % - (url, v.line) + 'down, %s' % (url, v.line) ) from v except (http.client.HTTPException, socket.error) as v: if warning: self.warn(warning, v) else: - raise DistutilsError("Download error for %s: %s" - % (url, v)) from v + raise DistutilsError("Download error for %s: %s" % (url, v)) from v def _download_url(self, scheme, url, tmpdir): # Determine download filename @@ -832,46 +848,16 @@ class PackageIndex(Environment): def _attempt_download(self, url, filename): headers = self._download_to(url, filename) if 'html' in headers.get('content-type', '').lower(): - return self._download_html(url, headers, filename) + return self._invalid_download_html(url, headers, filename) else: return filename - def _download_html(self, url, headers, filename): - file = open(filename) - for line in file: - if line.strip(): - # Check for a subversion index page - if re.search(r'<title>([^- ]+ - )?Revision \d+:', line): - # it's a subversion index page: - file.close() - os.unlink(filename) - return self._download_svn(url, filename) - break # not an index page - file.close() + def _invalid_download_html(self, url, headers, filename): os.unlink(filename) - raise DistutilsError("Unexpected HTML page found at " + url) - - def _download_svn(self, url, filename): - warnings.warn("SVN download support is deprecated", UserWarning) - url = url.split('#', 1)[0] # remove any fragment for svn's sake - creds = '' - if url.lower().startswith('svn:') and '@' in url: - scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) - if not netloc and path.startswith('//') and '/' in path[2:]: - netloc, path = path[2:].split('/', 1) - auth, host = _splituser(netloc) - if auth: - if ':' in auth: - user, pw = auth.split(':', 1) - creds = " --username=%s --password=%s" % (user, pw) - else: - creds = " --username=" + auth - netloc = host - parts = scheme, netloc, url, p, q, f - url = urllib.parse.urlunparse(parts) - self.info("Doing subversion checkout from %s to %s", url, filename) - os.system("svn checkout%s -q %s %s" % (creds, url, filename)) - return filename + raise DistutilsError(f"Unexpected HTML page found at {url}") + + def _download_svn(self, url, _filename): + raise DistutilsError(f"Invalid config, SVN download is not supported: {url}") @staticmethod def _vcs_split_rev_from_url(url, pop_prefix=False): @@ -900,10 +886,13 @@ class PackageIndex(Environment): if rev is not None: self.info("Checking out %s", rev) - os.system("git -C %s checkout --quiet %s" % ( - filename, - rev, - )) + os.system( + "git -C %s checkout --quiet %s" + % ( + filename, + rev, + ) + ) return filename @@ -916,10 +905,13 @@ class PackageIndex(Environment): if rev is not None: self.info("Updating to %s", rev) - os.system("hg --cwd %s up -C -r %s -q" % ( - filename, - rev, - )) + os.system( + "hg --cwd %s up -C -r %s -q" + % ( + filename, + rev, + ) + ) return filename @@ -1014,7 +1006,7 @@ class PyPIConfig(configparser.RawConfigParser): Load from ~/.pypirc """ defaults = dict.fromkeys(['username', 'password', 'repository'], '') - configparser.RawConfigParser.__init__(self, defaults) + super().__init__(defaults) rc = os.path.join(os.path.expanduser('~'), '.pypirc') if os.path.exists(rc): @@ -1023,7 +1015,8 @@ class PyPIConfig(configparser.RawConfigParser): @property def creds_by_repository(self): sections_with_repositories = [ - section for section in self.sections() + section + for section in self.sections() if self.get(section, 'repository').strip() ] @@ -1127,8 +1120,8 @@ def local_open(url): files.append('<a href="{name}">{name}</a>'.format(name=f)) else: tmpl = ( - "<html><head><title>{url}</title>" - "</head><body>{files}</body></html>") + "<html><head><title>{url}</title>" "</head><body>{files}</body></html>" + ) body = tmpl.format(url=url, files='\n'.join(files)) status, message = 200, "OK" else: |