Merge branch 'master' into topic/remove-conditionals

This commit is contained in:
Brian Cristante 2018-10-15 09:44:40 -04:00
commit 543a4613f4
87 changed files with 1101 additions and 613 deletions

View File

@ -1,5 +1,4 @@
language: python
sudo: false
cache: pip
dist: trusty
@ -41,11 +40,9 @@ jobs:
- env: GROUP=1
python: 3.7
dist: xenial
sudo: required
- env: GROUP=2
python: 3.7
dist: xenial
sudo: required
- env: GROUP=1
python: 3.5
- env: GROUP=2
@ -57,8 +54,10 @@ jobs:
- env: GROUP=1
python: 3.8-dev
dist: xenial
- env: GROUP=2
python: 3.8-dev
dist: xenial
# It's okay to fail on the in-development CPython version.
fast_finish: true

View File

@ -12,6 +12,7 @@ Alexandre Conrad <alexandre.conrad@gmail.com>
Alli <alzeih@users.noreply.github.com>
Anatoly Techtonik <techtonik@gmail.com>
Andrei Geacar <andrei.geacar@gmail.com>
Andrew Gaul <andrew@gaul.org>
Andrey Bulgakov <mail@andreiko.ru>
Andrés Delfino <34587441+andresdelfino@users.noreply.github.com>
Andrés Delfino <adelfino@gmail.com>
@ -36,6 +37,8 @@ Ashley Manton <ajd.manton@googlemail.com>
Atsushi Odagiri <aodagx@gmail.com>
Avner Cohen <israbirding@gmail.com>
Baptiste Mispelon <bmispelon@gmail.com>
Barney Gale <barney.gale@gmail.com>
barneygale <barney.gale@gmail.com>
Bartek Ogryczak <b.ogryczak@gmail.com>
Bastian Venthur <mail@venthur.de>
Ben Darnell <ben@bendarnell.com>
@ -46,6 +49,7 @@ Benjamin VanEvery <ben@simondata.com>
Benoit Pierre <benoit.pierre@gmail.com>
Berker Peksag <berker.peksag@gmail.com>
Bernardo B. Marques <bernardo.fire@gmail.com>
Bernhard M. Wiedemann <bwiedemann@suse.de>
Bogdan Opanchuk <bogdan@opanchuk.net>
Brad Erickson <eosrei@gmail.com>
Bradley Ayers <bradley.ayers@gmail.com>
@ -55,6 +59,7 @@ Brian Rosner <brosner@gmail.com>
BrownTruck <BrownTruck@users.noreply.github.com>
Bruno Oliveira <nicoddemus@gmail.com>
Bruno Renié <brutasse@gmail.com>
Bstrdsmkr <bstrdsmkr@gmail.com>
Buck Golemon <buck@yelp.com>
burrows <burrows@preveil.com>
Bussonnier Matthias <bussonniermatthias@gmail.com>
@ -157,6 +162,7 @@ Herbert Pfennig <herbert@albinen.com>
Hsiaoming Yang <lepture@me.com>
Hugo <hugovk@users.noreply.github.com>
Hugo Lopes Tavares <hltbra@gmail.com>
hugovk <hugovk@users.noreply.github.com>
Hynek Schlawack <hs@ox.cx>
Ian Bicking <ianb@colorstudy.com>
Ian Cordasco <graffatcolmingov@gmail.com>
@ -182,6 +188,7 @@ Jannis Leidel <jannis@leidel.info>
jarondl <me@jarondl.net>
Jason R. Coombs <jaraco@jaraco.com>
Jay Graves <jay@skabber.com>
Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>
Jeff Barber <jbarber@computer.org>
Jeff Dairiki <dairiki@dairiki.org>
Jeremy Stanley <fungi@yuggoth.org>
@ -194,6 +201,7 @@ Jon Dufresne <jon.dufresne@gmail.com>
Jon Parise <jon@indelible.org>
Jon Wayne Parrott <jjramone13@gmail.com>
Jonas Nockert <jonasnockert@gmail.com>
Jonathan Herbert <foohyfooh@gmail.com>
Joost Molenaar <j.j.molenaar@gmail.com>
Jorge Niedbalski <niedbalski@gmail.com>
Joseph Long <jdl@fastmail.fm>
@ -219,10 +227,12 @@ kpinc <kop@meme.com>
Kumar McMillan <kumar.mcmillan@gmail.com>
Kyle Persohn <kyle.persohn@gmail.com>
Laurent Bristiel <laurent@bristiel.com>
Laurie Opperman <laurie@sitesee.com.au>
Leon Sasson <leonsassonha@gmail.com>
Lev Givon <lev@columbia.edu>
Lincoln de Sousa <lincoln@comum.org>
Lipis <lipiridis@gmail.com>
Loren Carvalho <lcarvalho@linkedin.com>
Lucas Cimon <lucas.cimon@gmail.com>
Ludovic Gasc <gmludo@gmail.com>
Luke Macken <lmacken@redhat.com>
@ -259,6 +269,7 @@ Michael E. Karpeles <michael.karpeles@gmail.com>
Michael Klich <michal@michalklich.com>
Michael Williamson <mike@zwobble.org>
michaelpacer <michaelpacer@gmail.com>
Mickaël Schoentgen <mschoentgen@nuxeo.com>
Miguel Araujo Perez <miguel.araujo.perez@gmail.com>
Mihir Singh <git.service@mihirsingh.com>
Min RK <benjaminrk@gmail.com>
@ -272,6 +283,7 @@ Nehal J Wani <nehaljw.kkd1@gmail.com>
Nick Coghlan <ncoghlan@gmail.com>
Nick Stenning <nick@whiteink.com>
Nikhil Benesch <nikhil.benesch@gmail.com>
Nitesh Sharma <nbsharma@outlook.com>
Nowell Strite <nowell@strite.org>
nvdv <modestdev@gmail.com>
Ofekmeister <ofekmeister@gmail.com>
@ -380,6 +392,7 @@ Tomer Chachamu <tomer.chachamu@gmail.com>
Tony Zhaocheng Tan <tony@tonytan.io>
Toshio Kuratomi <toshio@fedoraproject.org>
Travis Swicegood <development@domain51.com>
Tzu-ping Chung <uranusjr@gmail.com>
Valentin Haenel <valentin.haenel@gmx.de>
Victor Stinner <victor.stinner@gmail.com>
Viktor Szépe <viktor@szepe.net>

View File

@ -7,6 +7,53 @@
.. towncrier release notes start
18.1 (2018-10-05)
=================
Features
--------
- Allow PEP 508 URL requirements to be used as dependencies.
As a security measure, pip will raise an exception when installing packages from
PyPI if those packages depend on packages not also hosted on PyPI.
In the future, PyPI will block uploading packages with such external URL dependencies directly. (`#4187 <https://github.com/pypa/pip/issues/4187>`_)
- Upgrade pyparsing to 2.2.1. (`#5013 <https://github.com/pypa/pip/issues/5013>`_)
- Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target (`#5355 <https://github.com/pypa/pip/issues/5355>`_)
- Support passing ``svn+ssh`` URLs with a username to ``pip install -e``. (`#5375 <https://github.com/pypa/pip/issues/5375>`_)
- pip now ensures that the RECORD file is sorted when installing from a wheel file. (`#5525 <https://github.com/pypa/pip/issues/5525>`_)
- Add support for Python 3.7. (`#5561 <https://github.com/pypa/pip/issues/5561>`_)
- Malformed configuration files now show helpful error messages, instead of tracebacks. (`#5798 <https://github.com/pypa/pip/issues/5798>`_)
Bug Fixes
---------
- Checkout the correct branch when doing an editable Git install. (`#2037 <https://github.com/pypa/pip/issues/2037>`_)
- Run self-version-check only on commands that may access the index, instead of
trying on every run and failing to do so due to missing options. (`#5433 <https://github.com/pypa/pip/issues/5433>`_)
- Allow a Git ref to be installed over an existing installation. (`#5624 <https://github.com/pypa/pip/issues/5624>`_)
- Show a better error message when a configuration option has an invalid value. (`#5644 <https://github.com/pypa/pip/issues/5644>`_)
- Always revalidate cached simple API pages instead of blindly caching them for up to 10
minutes. (`#5670 <https://github.com/pypa/pip/issues/5670>`_)
- Avoid caching self-version-check information when cache is disabled. (`#5679 <https://github.com/pypa/pip/issues/5679>`_)
- Avoid traceback printing on autocomplete after flags in the CLI. (`#5751 <https://github.com/pypa/pip/issues/5751>`_)
- Fix incorrect parsing of egg names if pip needs to guess the package name. (`#5819 <https://github.com/pypa/pip/issues/5819>`_)
Vendored Libraries
------------------
- Upgrade certifi to 2018.8.24
- Upgrade packaging to 18.0
- Add pep517 version 0.2
- Upgrade pytoml to 0.1.19
- Upgrade pkg_resources to 40.4.3 (via setuptools)
Improved Documentation
----------------------
- Fix "Requirements Files" reference in User Guide (`#user_guide_fix_requirements_file_ref <https://github.com/pypa/pip/issues/user_guide_fix_requirements_file_ref>`_)
18.0 (2018-07-22)
=================
@ -17,7 +64,7 @@ Process
- Formally document our deprecation process as a minimum of 6 months of deprecation
warnings.
- Adopt and document NEWS fragment writing style.
- Switch to releasing a new, non bug fix version of pip every 3 months.
- Switch to releasing a new, non-bug fix version of pip every 3 months.
Deprecations and Removals
-------------------------
@ -118,7 +165,7 @@ Bug Fixes
---------
- Prevent false-positive installation warnings due to incomplete name
normalizaton. (#5134)
normalization. (#5134)
- Fix issue where installing from Git with a short SHA would fail. (#5140)
- Accept pre-release versions when checking for conflicts with pip check or pip
install. (#5141)
@ -1207,7 +1254,7 @@ Improved Documentation
- **Dropped support for Python 2.4** The minimum supported Python version is
now Python 2.5.
- Fixed pypi mirror support being broken on some DNS responses. Thanks
- Fixed PyPI mirror support being broken on some DNS responses. Thanks
philwhin. (#605)
- Fixed pip uninstall removing files it didn't install. Thanks pjdelport.
(#355)

View File

@ -1,6 +1,8 @@
environment:
matrix:
# Unit and integration tests.
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python36-x64"
- PYTHON: "C:\\Python27"
RUN_INTEGRATION_TESTS: "True"
- PYTHON: "C:\\Python36-x64"
@ -58,8 +60,10 @@ test_script:
subst T: $env:TEMP
$env:TEMP = "T:\"
$env:TMP = "T:\"
tox -e py -- -m unit -n 3
if ($env:RUN_INTEGRATION_TESTS -eq "True") {
tox -e py -- -m integration -n 3 --duration=5
}
else {
tox -e py -- -m unit -n 3
}
}

View File

@ -23,8 +23,6 @@ To install pip, securely download `get-pip.py
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
As when running any script downloaded from the web, ensure that you have
reviewed the code and are happy that it works as you expect.
Then run the following::
python get-pip.py

View File

@ -1,5 +0,0 @@
Allow PEP 508 URL requirements to be used as dependencies.
As a security measure, pip will raise an exception when installing packages from
PyPI if those packages depend on packages not also hosted on PyPI.
In the future, PyPI will block uploading packages with such external URL dependencies directly.

View File

@ -1 +0,0 @@
Limit progress bar update interval to 200 ms.

2
news/5270.bugfix Normal file
View File

@ -0,0 +1,2 @@
Handle `requests.exceptions.RetryError` raised in `PackageFinder` that was
causing pip to fail silently when some indexes were unreachable.

View File

@ -1 +0,0 @@
Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target

View File

@ -1 +0,0 @@
Support passing ``svn+ssh`` URLs with a username to ``pip install -e``.

View File

@ -1,2 +0,0 @@
Run self-version-check only on commands that may access the index, instead of
trying on every run and failing to do so due to missing options.

2
news/5483.bugfix Normal file
View File

@ -0,0 +1,2 @@
Handle `requests.exceptions.RetryError` raised in `PackageFinder` that was
causing pip to fail silently when some indexes were unreachable.

View File

@ -1 +0,0 @@
pip now ensures that the RECORD file is sorted when installing from a wheel file.

View File

@ -1 +0,0 @@
Add support for Python 3.7.

View File

@ -1 +0,0 @@
Allow a Git ref to be installed over an existing installation.

View File

@ -1 +0,0 @@
Show a better error message when a configuration option has an invalid value.

View File

@ -1 +0,0 @@
Avoid caching self-version-check information when cache is disabled.

View File

@ -1 +0,0 @@
Remove the unmatched bracket in the --no-clean option's help text.

View File

@ -1 +0,0 @@
Fix links to NEWS entry guidelines.

1
news/5827.feature Normal file
View File

@ -0,0 +1 @@
A warning message is emitted when dropping an ``--[extra-]index-url`` value that points to an existing local directory.

1
news/5838.bugfix Normal file
View File

@ -0,0 +1 @@
Fix content type detection if a directory named like an archive is used as a package source.

2
news/5866.removal Normal file
View File

@ -0,0 +1,2 @@
Remove the deprecated SVN editable detection based on dependency links
during freeze.

View File

@ -1 +0,0 @@
Add pep517 version 0.2

View File

@ -1 +0,0 @@
Fix "Requirements Files" reference in User Guide

View File

@ -1 +1 @@
__version__ = "18.1.dev0"
__version__ = "19.0.dev0"

View File

@ -116,7 +116,8 @@ def get_path_completion_type(cwords, cword, opts):
continue
for o in str(opt).split('/'):
if cwords[cword - 2].split('=')[0] == o:
if any(x in ('path', 'file', 'dir')
if not opt.metavar or any(
x in ('path', 'file', 'dir')
for x in opt.metavar.split('/')):
return opt.metavar

View File

@ -9,6 +9,7 @@ from distutils.util import strtobool
from pip._vendor.six import string_types
from pip._internal.cli.status_codes import UNKNOWN_ERROR
from pip._internal.configuration import Configuration, ConfigurationError
from pip._internal.utils.compat import get_terminal_size
@ -232,7 +233,7 @@ class ConfigOptionParser(CustomOptionParser):
try:
self.config.load()
except ConfigurationError as err:
self.exit(2, err.args[0])
self.exit(UNKNOWN_ERROR, str(err))
defaults = self._update_defaults(self.defaults.copy()) # ours
for option in self._get_all_options():
@ -244,7 +245,7 @@ class ConfigOptionParser(CustomOptionParser):
def error(self, msg):
self.print_usage(sys.stderr)
self.exit(2, "%s\n" % msg)
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
def invalid_config_error_message(action, key, val):

View File

@ -18,7 +18,9 @@ import os
from pip._vendor import six
from pip._vendor.six.moves import configparser
from pip._internal.exceptions import ConfigurationError
from pip._internal.exceptions import (
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
)
from pip._internal.locations import (
legacy_config_file, new_config_file, running_under_virtualenv,
site_config_files, venv_config_file,
@ -289,11 +291,16 @@ class Configuration(object):
try:
parser.read(fname)
except UnicodeDecodeError:
raise ConfigurationError((
"ERROR: "
"Configuration file contains invalid %s characters.\n"
"Please fix your configuration, located at %s\n"
) % (locale.getpreferredencoding(False), fname))
# See https://github.com/pypa/pip/issues/4963
raise ConfigurationFileCouldNotBeLoaded(
reason="contains invalid {} characters".format(
locale.getpreferredencoding(False)
),
fname=fname,
)
except configparser.Error as error:
# See https://github.com/pypa/pip/issues/4893
raise ConfigurationFileCouldNotBeLoaded(error=error)
return parser
def _load_environment_vars(self):

View File

@ -247,3 +247,22 @@ class HashMismatch(HashError):
class UnsupportedPythonVersion(InstallationError):
"""Unsupported python version according to Requires-Python package
metadata."""
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
"""When there are errors while loading a configuration file
"""
def __init__(self, reason="could not be loaded", fname=None, error=None):
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
self.reason = reason
self.fname = fname
self.error = error
def __str__(self):
if self.fname is not None:
message_part = " in {}.".format(self.fname)
else:
assert self.error is not None
message_part = ".\n{}\n".format(self.error.message)
return "Configuration file {}{}".format(self.reason, message_part)

View File

@ -16,7 +16,7 @@ from pip._vendor.distlib.compat import unescape
from pip._vendor.packaging import specifiers
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import parse as parse_version
from pip._vendor.requests.exceptions import SSLError
from pip._vendor.requests.exceptions import RetryError, SSLError
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._vendor.six.moves.urllib import request as urllib_request
@ -34,7 +34,7 @@ from pip._internal.utils.compat import ipaddress
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, cached_property, normalize_path,
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, normalize_path,
remove_auth_from_url,
)
from pip._internal.utils.packaging import check_requires_python
@ -59,6 +59,173 @@ SECURE_ORIGINS = [
logger = logging.getLogger(__name__)
def _match_vcs_scheme(url):
"""Look for VCS schemes in the URL.
Returns the matched VCS scheme, or None if there's no match.
"""
from pip._internal.vcs import VcsSupport
for scheme in VcsSupport.schemes:
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
return scheme
return None
def _is_url_like_archive(url):
"""Return whether the URL looks like an archive.
"""
filename = Link(url).filename
for bad_ext in ARCHIVE_EXTENSIONS:
if filename.endswith(bad_ext):
return True
return False
class _NotHTML(Exception):
def __init__(self, content_type, request_desc):
super(_NotHTML, self).__init__(content_type, request_desc)
self.content_type = content_type
self.request_desc = request_desc
def _ensure_html_header(response):
"""Check the Content-Type header to ensure the response contains HTML.
Raises `_NotHTML` if the content type is not text/html.
"""
content_type = response.headers.get("Content-Type", "")
if not content_type.lower().startswith("text/html"):
raise _NotHTML(content_type, response.request.method)
class _NotHTTP(Exception):
pass
def _ensure_html_response(url, session):
"""Send a HEAD request to the URL, and ensure the response contains HTML.
Raises `_NotHTTP` if the URL is not available for a HEAD request, or
`_NotHTML` if the content type is not text/html.
"""
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
if scheme not in {'http', 'https'}:
raise _NotHTTP()
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
_ensure_html_header(resp)
def _get_html_response(url, session):
"""Access an HTML page with GET, and return the response.
This consists of three parts:
1. If the URL looks suspiciously like an archive, send a HEAD first to
check the Content-Type is HTML, to avoid downloading a large file.
Raise `_NotHTTP` if the content type cannot be determined, or
`_NotHTML` if it is not HTML.
2. Actually perform the request. Raise HTTP exceptions on network failures.
3. Check the Content-Type header to make sure we got HTML, and raise
`_NotHTML` otherwise.
"""
if _is_url_like_archive(url):
_ensure_html_response(url, session=session)
logger.debug('Getting page %s', url)
resp = session.get(
url,
headers={
"Accept": "text/html",
# We don't want to blindly returned cached data for
# /simple/, because authors generally expecting that
# twine upload && pip install will function, but if
# they've done a pip install in the last ~10 minutes
# it won't. Thus by setting this to zero we will not
# blindly use any cached data, however the benefit of
# using max-age=0 instead of no-cache, is that we will
# still support conditional requests, so we will still
# minimize traffic sent in cases where the page hasn't
# changed at all, we will just always incur the round
# trip for the conditional GET now instead of only
# once per 10 minutes.
# For more information, please see pypa/pip#5670.
"Cache-Control": "max-age=0",
},
)
resp.raise_for_status()
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
# url we cannot know ahead of time for sure if something is HTML
# or not. However we can check after we've downloaded it.
_ensure_html_header(resp)
return resp
def _handle_get_page_fail(link, reason, url, meth=None):
if meth is None:
meth = logger.debug
meth("Could not fetch URL %s: %s - skipping", link, reason)
def _get_html_page(link, session=None):
if session is None:
raise TypeError(
"_get_html_page() missing 1 required keyword argument: 'session'"
)
url = link.url.split('#', 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
vcs_scheme = _match_vcs_scheme(url)
if vcs_scheme:
logger.debug('Cannot look at %s URL %s', vcs_scheme, link)
return None
# Tack index.html onto file:// URLs that point to directories
scheme, _, path, _, _, _ = urllib_parse.urlparse(url)
if (scheme == 'file' and os.path.isdir(urllib_request.url2pathname(path))):
# add trailing slash if not present so urljoin doesn't trim
# final segment
if not url.endswith('/'):
url += '/'
url = urllib_parse.urljoin(url, 'index.html')
logger.debug(' file: URL is directory, getting %s', url)
try:
resp = _get_html_response(url, session=session)
except _NotHTTP as exc:
logger.debug(
'Skipping page %s because it looks like an archive, and cannot '
'be checked by HEAD.', link,
)
except _NotHTML as exc:
logger.debug(
'Skipping page %s because the %s request got Content-Type: %s',
link, exc.request_desc, exc.content_type,
)
except requests.HTTPError as exc:
_handle_get_page_fail(link, exc, url)
except RetryError as exc:
_handle_get_page_fail(link, exc, url)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
_handle_get_page_fail(link, reason, url, meth=logger.info)
except requests.ConnectionError as exc:
_handle_get_page_fail(link, "connection error: %s" % exc, url)
except requests.Timeout:
_handle_get_page_fail(link, "timed out", url)
else:
return HTMLPage(resp.content, resp.url, resp.headers)
class PackageFinder(object):
"""This finds packages.
@ -177,7 +344,7 @@ class PackageFinder(object):
"Dependency Links processing has been deprecated and will be "
"removed in a future release.",
replacement="PEP 508 URL dependencies",
gone_in="18.2",
gone_in="19.0",
issue=4187,
)
self.dependency_links.extend(links)
@ -216,6 +383,11 @@ class PackageFinder(object):
sort_path(os.path.join(path, item))
elif is_file_url:
urls.append(url)
else:
logger.warning(
"Path '{0}' is ignored: "
"it is a directory.".format(path),
)
elif os.path.isfile(path):
sort_path(path)
else:
@ -415,7 +587,7 @@ class PackageFinder(object):
logger.debug('Analyzing links from page %s', page.url)
with indent_log():
page_versions.extend(
self._package_versions(page.links, search)
self._package_versions(page.iter_links(), search)
)
dependency_versions = self._package_versions(
@ -556,7 +728,7 @@ class PackageFinder(object):
continue
seen.add(location)
page = self._get_page(location)
page = _get_html_page(location, session=self.session)
if page is None:
continue
@ -673,9 +845,6 @@ class PackageFinder(object):
return InstallationCandidate(search.supplied, version, link)
def _get_page(self, link):
return HTMLPage.get_page(link, session=self.session)
def egg_info_matches(
egg_info, search_name, link,
@ -694,7 +863,7 @@ def egg_info_matches(
return None
if search_name is None:
full_match = match.group(0)
return full_match[full_match.index('-'):]
return full_match.split('-', 1)[-1]
name = match.group(0).lower()
# To match the "safe" name that pkg_resources creates:
name = name.replace('_', '-')
@ -706,169 +875,71 @@ def egg_info_matches(
return None
def _determine_base_url(document, page_url):
"""Determine the HTML document's base URL.
This looks for a ``<base>`` tag in the HTML document. If present, its href
attribute denotes the base URL of anchor tags in the document. If there is
no such tag (or if it does not have a valid href attribute), the HTML
file's URL is used as the base URL.
:param document: An HTML document representation. The current
implementation expects the result of ``html5lib.parse()``.
:param page_url: The URL of the HTML document.
"""
for base in document.findall(".//base"):
href = base.get("href")
if href is not None:
return href
return page_url
def _get_encoding_from_headers(headers):
"""Determine if we have any encoding information in our headers.
"""
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
return params['charset']
return None
_CLEAN_LINK_RE = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
def _clean_link(url):
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
the link, it will be rewritten to %20 (while not over-quoting
% or other characters)."""
return _CLEAN_LINK_RE.sub(lambda match: '%%%2x' % ord(match.group(0)), url)
class HTMLPage(object):
"""Represents one page, along with its URL"""
def __init__(self, content, url, headers=None):
# Determine if we have any encoding information in our headers
encoding = None
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
encoding = params['charset']
self.content = content
self.parsed = html5lib.parse(
self.content,
transport_encoding=encoding,
namespaceHTMLElements=False,
)
self.url = url
self.headers = headers
def __str__(self):
return self.url
@classmethod
def get_page(cls, link, skip_archives=True, session=None):
if session is None:
raise TypeError(
"get_page() missing 1 required keyword argument: 'session'"
)
url = link.url
url = url.split('#', 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
from pip._internal.vcs import VcsSupport
for scheme in VcsSupport.schemes:
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
logger.debug('Cannot look at %s URL %s', scheme, link)
return None
try:
if skip_archives:
filename = link.filename
for bad_ext in ARCHIVE_EXTENSIONS:
if filename.endswith(bad_ext):
content_type = cls._get_content_type(
url, session=session,
)
if content_type.lower().startswith('text/html'):
break
else:
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
logger.debug('Getting page %s', url)
# Tack index.html onto file:// URLs that point to directories
(scheme, netloc, path, params, query, fragment) = \
urllib_parse.urlparse(url)
if (scheme == 'file' and
os.path.isdir(urllib_request.url2pathname(path))):
# add trailing slash if not present so urljoin doesn't trim
# final segment
if not url.endswith('/'):
url += '/'
url = urllib_parse.urljoin(url, 'index.html')
logger.debug(' file: URL is directory, getting %s', url)
resp = session.get(
url,
headers={
"Accept": "text/html",
"Cache-Control": "max-age=600",
},
)
resp.raise_for_status()
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
# requirement of an url. Unless we issue a HEAD request on every
# url we cannot know ahead of time for sure if something is HTML
# or not. However we can check after we've downloaded it.
content_type = resp.headers.get('Content-Type', 'unknown')
if not content_type.lower().startswith("text/html"):
logger.debug(
'Skipping page %s because of Content-Type: %s',
link,
content_type,
)
return
inst = cls(resp.content, resp.url, resp.headers)
except requests.HTTPError as exc:
cls._handle_fail(link, exc, url)
except SSLError as exc:
reason = "There was a problem confirming the ssl certificate: "
reason += str(exc)
cls._handle_fail(link, reason, url, meth=logger.info)
except requests.ConnectionError as exc:
cls._handle_fail(link, "connection error: %s" % exc, url)
except requests.Timeout:
cls._handle_fail(link, "timed out", url)
else:
return inst
@staticmethod
def _handle_fail(link, reason, url, meth=None):
if meth is None:
meth = logger.debug
meth("Could not fetch URL %s: %s - skipping", link, reason)
@staticmethod
def _get_content_type(url, session):
"""Get the Content-Type of the given url, using a HEAD request"""
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
if scheme not in {'http', 'https'}:
# FIXME: some warning or something?
# assertion error?
return ''
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
return resp.headers.get("Content-Type", "")
@cached_property
def base_url(self):
bases = [
x for x in self.parsed.findall(".//base")
if x.get("href") is not None
]
if bases and bases[0].get("href"):
return bases[0].get("href")
else:
return self.url
@property
def links(self):
def iter_links(self):
"""Yields all links in the page"""
for anchor in self.parsed.findall(".//a"):
document = html5lib.parse(
self.content,
transport_encoding=_get_encoding_from_headers(self.headers),
namespaceHTMLElements=False,
)
base_url = _determine_base_url(document, self.url)
for anchor in document.findall(".//a"):
if anchor.get("href"):
href = anchor.get("href")
url = self.clean_link(
urllib_parse.urljoin(self.base_url, href)
)
url = _clean_link(urllib_parse.urljoin(base_url, href))
pyrequire = anchor.get('data-requires-python')
pyrequire = unescape(pyrequire) if pyrequire else None
yield Link(url, self, requires_python=pyrequire)
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
def clean_link(self, url):
"""Makes sure a link is fully encoded. That is, if a ' ' shows up in
the link, it will be rewritten to %20 (while not over-quoting
% or other characters)."""
return self._clean_re.sub(
lambda match: '%%%2x' % ord(match.group(0)), url)
yield Link(url, self.url, requires_python=pyrequire)
Search = namedtuple('Search', 'supplied canonical formats')

View File

@ -14,7 +14,6 @@ from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pip._internal.req.req_file import COMMENT_RE
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import (
dist_is_editable, get_installed_distributions,
)
@ -164,89 +163,49 @@ class FrozenRequirement(object):
self.editable = editable
self.comments = comments
_rev_re = re.compile(r'-r(\d+)$')
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
@classmethod
def _init_args_from_dist(cls, dist, dependency_links):
"""
Compute and return arguments (req, editable, comments) to pass to
FrozenRequirement.__init__().
This method is for use in FrozenRequirement.from_dist().
"""
location = os.path.normcase(os.path.abspath(dist.location))
from pip._internal.vcs import vcs, get_src_requirement
if not dist_is_editable(dist):
req = dist.as_requirement()
return (req, False, [])
vc_type = vcs.get_backend_type(location)
if not vc_type:
req = dist.as_requirement()
return (req, False, [])
try:
req = get_src_requirement(vc_type, dist, location)
except InstallationError as exc:
logger.warning(
"Error when trying to get requirement for VCS system %s, "
"falling back to uneditable format", exc
)
else:
if req is not None:
return (req, True, [])
logger.warning(
'Could not determine repository location of %s', location
)
comments = ['## !! Could not determine repository location']
req = dist.as_requirement()
return (req, False, comments)
@classmethod
def from_dist(cls, dist, dependency_links):
location = os.path.normcase(os.path.abspath(dist.location))
comments = []
from pip._internal.vcs import vcs, get_src_requirement
if dist_is_editable(dist) and vcs.get_backend_name(location):
editable = True
try:
req = get_src_requirement(dist, location)
except InstallationError as exc:
logger.warning(
"Error when trying to get requirement for VCS system %s, "
"falling back to uneditable format", exc
)
req = None
if req is None:
logger.warning(
'Could not determine repository location of %s', location
)
comments.append(
'## !! Could not determine repository location'
)
req = dist.as_requirement()
editable = False
else:
editable = False
req = dist.as_requirement()
specs = req.specs
assert len(specs) == 1 and specs[0][0] in ["==", "==="], \
'Expected 1 spec with == or ===; specs = %r; dist = %r' % \
(specs, dist)
version = specs[0][1]
ver_match = cls._rev_re.search(version)
date_match = cls._date_re.search(version)
if ver_match or date_match:
svn_backend = vcs.get_backend('svn')
if svn_backend:
svn_location = svn_backend().get_location(
dist,
dependency_links,
)
if not svn_location:
logger.warning(
'Warning: cannot find svn location for %s', req,
)
comments.append(
'## FIXME: could not find svn URL in dependency_links '
'for this package:'
)
else:
deprecated(
"SVN editable detection based on dependency links "
"will be dropped in the future.",
replacement=None,
gone_in="18.2",
issue=4187,
)
comments.append(
'# Installing as editable to satisfy requirement %s:' %
req
)
if ver_match:
rev = ver_match.group(1)
else:
rev = '{%s}' % date_match.group(1)
editable = True
req = '%s@%s#egg=%s' % (
svn_location,
rev,
cls.egg_name(dist)
)
return cls(dist.project_name, req, editable, comments)
@staticmethod
def egg_name(dist):
name = dist.egg_name()
match = re.search(r'-py\d\.\d$', name)
if match:
name = name[:match.start()]
return name
args = cls._init_args_from_dist(dist, dependency_links)
return cls(dist.project_name, *args)
def __str__(self):
req = self.req

View File

@ -11,6 +11,7 @@ import warnings
from collections import OrderedDict
import pip._internal.utils.glibc
from pip._internal.utils.compat import get_extension_suffixes
logger = logging.getLogger(__name__)
@ -252,10 +253,9 @@ def get_supported(versions=None, noarch=False, platform=None,
abis[0:0] = [abi]
abi3s = set()
import imp
for suffix in imp.get_suffixes():
if suffix[0].startswith('.abi'):
abi3s.add(suffix[0].split('.', 2)[1])
for suffix in get_extension_suffixes():
if suffix.startswith('.abi'):
abi3s.add(suffix.split('.', 2)[1])
abis.extend(sorted(list(abi3s)))

View File

@ -25,6 +25,7 @@ except ImportError:
__all__ = [
"ipaddress", "uses_pycache", "console_to_str", "native_str",
"get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size",
"get_extension_suffixes",
]
@ -160,6 +161,18 @@ def get_path_uid(path):
return file_uid
if sys.version_info >= (3, 4):
from importlib.machinery import EXTENSION_SUFFIXES
def get_extension_suffixes():
return EXTENSION_SUFFIXES
else:
from imp import get_suffixes
def get_extension_suffixes():
return [suffix[0] for suffix in get_suffixes()]
def expanduser(path):
"""
Expand ~ and ~user constructions.

View File

@ -520,15 +520,11 @@ def untar_file(filename, location):
mode = 'r:*'
tar = tarfile.open(filename, mode)
try:
# note: python<=2.5 doesn't seem to know about pax headers, filter them
leading = has_leading_dir([
member.name for member in tar.getmembers()
if member.name != 'pax_global_header'
])
for member in tar.getmembers():
fn = member.name
if fn == 'pax_global_header':
continue
if leading:
fn = split_leading_dir(fn)[1]
path = os.path.join(location, fn)
@ -856,6 +852,20 @@ def enum(*sequential, **named):
return type('Enum', (), enums)
def make_vcs_requirement_url(repo_url, rev, egg_project_name, subdir=None):
"""
Return the URL for a VCS requirement.
Args:
repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+").
"""
req = '{}@{}#egg={}'.format(repo_url, rev, egg_project_name)
if subdir:
req += '&subdirectory={}'.format(subdir)
return req
def split_auth_from_netloc(netloc):
"""
Parse out and remove the auth information from a netloc.

View File

@ -137,7 +137,6 @@ class DownloadProgressMixin(object):
def __init__(self, *args, **kwargs):
super(DownloadProgressMixin, self).__init__(*args, **kwargs)
self.message = (" " * (get_indentation() + 2)) + self.message
self.last_update = 0.0
@property
def downloaded(self):
@ -162,15 +161,6 @@ class DownloadProgressMixin(object):
self.next(n)
self.finish()
def update(self):
# limit updates to avoid swamping the TTY
now = time.time()
if now < self.last_update + 0.2:
return
self.last_update = now
super(DownloadProgressMixin, self).update()
class WindowsMixin(object):

View File

@ -133,16 +133,16 @@ class VcsSupport(object):
else:
logger.warning('Cannot unregister because no class or name given')
def get_backend_name(self, location):
def get_backend_type(self, location):
"""
Return the name of the version control backend if found at given
location, e.g. vcs.get_backend_name('/path/to/vcs/checkout')
Return the type of the version control backend if found at given
location, e.g. vcs.get_backend_type('/path/to/vcs/checkout')
"""
for vc_type in self._registry.values():
if vc_type.controls_location(location):
logger.debug('Determine that %s uses VCS: %s',
location, vc_type.name)
return vc_type.name
return vc_type
return None
def get_backend(self, name):
@ -150,12 +150,6 @@ class VcsSupport(object):
if name in self._registry:
return self._registry[name]
def get_backend_from_location(self, location):
vc_type = self.get_backend_name(location)
if vc_type:
return self.get_backend(vc_type)
return None
vcs = VcsSupport()
@ -487,23 +481,14 @@ class VersionControl(object):
return cls.is_repository_directory(location)
def get_src_requirement(dist, location):
version_control = vcs.get_backend_from_location(location)
if version_control:
try:
return version_control().get_src_requirement(dist,
location)
except BadCommand:
logger.warning(
'cannot determine version of editable source in %s '
'(%s command not found in path)',
location,
version_control.name,
)
return dist.as_requirement()
logger.warning(
'cannot determine version of editable source in %s (is not SVN '
'checkout, Git clone, Mercurial clone or Bazaar branch)',
location,
)
return dist.as_requirement()
def get_src_requirement(vc_type, dist, location):
try:
return vc_type().get_src_requirement(dist, location)
except BadCommand:
logger.warning(
'cannot determine version of editable source in %s '
'(%s command not found in path)',
location,
vc_type.name,
)
return dist.as_requirement()

View File

@ -6,7 +6,9 @@ import os
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.download import path_to_url
from pip._internal.utils.misc import display_path, rmtree
from pip._internal.utils.misc import (
display_path, make_vcs_requirement_url, rmtree,
)
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.vcs import VersionControl, vcs
@ -98,9 +100,9 @@ class Bazaar(VersionControl):
return None
if not repo.lower().startswith('bzr:'):
repo = 'bzr+' + repo
egg_project_name = dist.egg_name().split('-', 1)[0]
current_rev = self.get_revision(location)
return '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
egg_project_name = dist.egg_name().split('-', 1)[0]
return make_vcs_requirement_url(repo, current_rev, egg_project_name)
def is_commit_id_equal(self, dest, name):
"""Always assume the versions don't match"""

View File

@ -10,7 +10,7 @@ from pip._vendor.six.moves.urllib import request as urllib_request
from pip._internal.exceptions import BadCommand
from pip._internal.utils.compat import samefile
from pip._internal.utils.misc import display_path
from pip._internal.utils.misc import display_path, make_vcs_requirement_url
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.vcs import VersionControl, vcs
@ -77,6 +77,20 @@ class Git(VersionControl):
version = '.'.join(version.split('.')[:3])
return parse_version(version)
def get_branch(self, location):
"""
Return the current branch, or None if HEAD isn't at a branch
(e.g. detached HEAD).
"""
args = ['rev-parse', '--abbrev-ref', 'HEAD']
output = self.run_command(args, show_stdout=False, cwd=location)
branch = output.strip()
if branch == 'HEAD':
return None
return branch
def export(self, location):
"""Export the Git repository at the url to the destination location"""
if not location.endswith('/'):
@ -91,8 +105,8 @@ class Git(VersionControl):
def get_revision_sha(self, dest, rev):
"""
Return a commit hash for the given revision if it names a remote
branch or tag. Otherwise, return None.
Return (sha_or_none, is_branch), where sha_or_none is a commit hash
if the revision names a remote branch or tag, otherwise None.
Args:
dest: the repository directory.
@ -115,7 +129,13 @@ class Git(VersionControl):
branch_ref = 'refs/remotes/origin/{}'.format(rev)
tag_ref = 'refs/tags/{}'.format(rev)
return refs.get(branch_ref) or refs.get(tag_ref)
sha = refs.get(branch_ref)
if sha is not None:
return (sha, True)
sha = refs.get(tag_ref)
return (sha, False)
def resolve_revision(self, dest, url, rev_options):
"""
@ -126,10 +146,13 @@ class Git(VersionControl):
rev_options: a RevOptions object.
"""
rev = rev_options.arg_rev
sha = self.get_revision_sha(dest, rev)
sha, is_branch = self.get_revision_sha(dest, rev)
if sha is not None:
return rev_options.make_new(sha)
rev_options = rev_options.make_new(sha)
rev_options.branch_name = rev if is_branch else None
return rev_options
# Do not show a warning for the common case of something that has
# the form of a Git commit hash.
@ -177,10 +200,20 @@ class Git(VersionControl):
if rev_options.rev:
# Then a specific revision was requested.
rev_options = self.resolve_revision(dest, url, rev_options)
# Only do a checkout if the current commit id doesn't match
# the requested revision.
if not self.is_commit_id_equal(dest, rev_options.rev):
cmd_args = ['checkout', '-q'] + rev_options.to_args()
branch_name = getattr(rev_options, 'branch_name', None)
if branch_name is None:
# Only do a checkout if the current commit id doesn't match
# the requested revision.
if not self.is_commit_id_equal(dest, rev_options.rev):
cmd_args = ['checkout', '-q'] + rev_options.to_args()
self.run_command(cmd_args, cwd=dest)
elif self.get_branch(dest) != branch_name:
# Then a specific branch was requested, and that branch
# is not yet checked out.
track_branch = 'origin/{}'.format(branch_name)
cmd_args = [
'checkout', '-b', branch_name, '--track', track_branch,
]
self.run_command(cmd_args, cwd=dest)
#: repo may contain submodules
@ -261,14 +294,12 @@ class Git(VersionControl):
repo = self.get_url(location)
if not repo.lower().startswith('git:'):
repo = 'git+' + repo
egg_project_name = dist.egg_name().split('-', 1)[0]
if not repo:
return None
current_rev = self.get_revision(location)
req = '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
subdirectory = self._get_subdirectory(location)
if subdirectory:
req += '&subdirectory=' + subdirectory
egg_project_name = dist.egg_name().split('-', 1)[0]
subdir = self._get_subdirectory(location)
req = make_vcs_requirement_url(repo, current_rev, egg_project_name,
subdir=subdir)
return req
def get_url_rev_and_auth(self, url):

View File

@ -6,7 +6,7 @@ import os
from pip._vendor.six.moves import configparser
from pip._internal.download import path_to_url
from pip._internal.utils.misc import display_path
from pip._internal.utils.misc import display_path, make_vcs_requirement_url
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.vcs import VersionControl, vcs
@ -88,11 +88,10 @@ class Mercurial(VersionControl):
repo = self.get_url(location)
if not repo.lower().startswith('hg:'):
repo = 'hg+' + repo
egg_project_name = dist.egg_name().split('-', 1)[0]
if not repo:
return None
current_rev_hash = self.get_revision_hash(location)
return '%s@%s#egg=%s' % (repo, current_rev_hash, egg_project_name)
egg_project_name = dist.egg_name().split('-', 1)[0]
return make_vcs_requirement_url(repo, current_rev_hash,
egg_project_name)
def is_commit_id_equal(self, dest, name):
"""Always assume the versions don't match"""

View File

@ -7,7 +7,7 @@ import re
from pip._internal.models.link import Link
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
display_path, rmtree, split_auth_from_netloc,
display_path, make_vcs_requirement_url, rmtree, split_auth_from_netloc,
)
from pip._internal.vcs import VersionControl, vcs
@ -199,10 +199,11 @@ class Subversion(VersionControl):
repo = self.get_url(location)
if repo is None:
return None
repo = 'svn+' + repo
rev = self.get_revision(location)
# FIXME: why not project name?
egg_project_name = dist.egg_name().split('-', 1)[0]
rev = self.get_revision(location)
return 'svn+%s@%s#egg=%s' % (repo, rev, egg_project_name)
return make_vcs_requirement_url(repo, rev, egg_project_name)
def is_commit_id_equal(self, dest, name):
"""Always assume the versions don't match"""

View File

@ -475,7 +475,7 @@ if __name__ == '__main__':
if warn_script_location:
msg = message_about_scripts_not_on_PATH(generated_console_scripts)
if msg is not None:
logger.warn(msg)
logger.warning(msg)
if len(gui) > 0:
generated.extend(

View File

@ -1,3 +1,3 @@
from .core import where, old_where
__version__ = "2018.04.16"
__version__ = "2018.08.24"

View File

@ -3692,169 +3692,6 @@ lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----
# Issuer: CN=Certplus Root CA G1 O=Certplus
# Subject: CN=Certplus Root CA G1 O=Certplus
# Label: "Certplus Root CA G1"
# Serial: 1491911565779898356709731176965615564637713
# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42
# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66
# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a
iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt
6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP
0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f
6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE
EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN
1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc
h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT
mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV
4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO
WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd
Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq
hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh
66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7
/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS
S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j
2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R
Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr
RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy
6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV
V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5
g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl
++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo=
-----END CERTIFICATE-----
# Issuer: CN=Certplus Root CA G2 O=Certplus
# Subject: CN=Certplus Root CA G2 O=Certplus
# Label: "Certplus Root CA G2"
# Serial: 1492087096131536844209563509228951875861589
# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31
# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a
# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17
-----BEGIN CERTIFICATE-----
MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat
93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x
Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P
AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj
FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG
SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch
p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal
U5ORGpOucGpnutee5WEaXw==
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust
# Subject: CN=OpenTrust Root CA G1 O=OpenTrust
# Label: "OpenTrust Root CA G1"
# Serial: 1492036577811947013770400127034825178844775
# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da
# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e
# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4
-----BEGIN CERTIFICATE-----
MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b
wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX
/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0
77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP
uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx
p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx
Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2
TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W
G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw
vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY
EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1
2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw
DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E
PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf
gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS
FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0
V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P
XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I
i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t
TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91
09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky
Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ
AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj
1oxx
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust
# Subject: CN=OpenTrust Root CA G2 O=OpenTrust
# Label: "OpenTrust Root CA G2"
# Serial: 1492012448042702096986875987676935573415441
# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb
# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b
# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2
-----BEGIN CERTIFICATE-----
MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh
/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e
CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6
1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE
FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS
gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X
G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy
YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH
vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4
t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/
gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3
5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w
DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz
Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0
nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT
RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT
wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2
t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa
TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2
o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU
3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA
iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f
WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM
S1IK
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust
# Subject: CN=OpenTrust Root CA G3 O=OpenTrust
# Label: "OpenTrust Root CA G3"
# Serial: 1492104908271485653071219941864171170455615
# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24
# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6
# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92
-----BEGIN CERTIFICATE-----
MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx
CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U
cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow
QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl
blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm
3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d
oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5
DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK
BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q
j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx
4nxp5V2a+EEfOzmTk51V6s2N8fvB
-----END CERTIFICATE-----
# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
# Subject: CN=ISRG Root X1 O=Internet Security Research Group
# Label: "ISRG Root X1"
@ -4398,3 +4235,66 @@ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
# Label: "GlobalSign Root CA - R6"
# Serial: 1417766617973444989252670301619537
# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----
# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
# Label: "OISTE WISeKey Global Root GC CA"
# Serial: 44084345621038548146064804565436152554
# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----

View File

@ -12,10 +12,10 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__version__ = "17.1"
__version__ = "18.0"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
__copyright__ = "Copyright 2014-2016 %s" % __author__
__copyright__ = "Copyright 2014-2018 %s" % __author__

View File

@ -92,16 +92,16 @@ class Requirement(object):
try:
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement(
"Invalid requirement, parse error at \"{0!r}\"".format(
requirement_string[e.loc:e.loc + 8]))
raise InvalidRequirement("Parse error at \"{0!r}\": {1}".format(
requirement_string[e.loc:e.loc + 8], e.msg
))
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
if not (parsed_url.scheme and parsed_url.netloc) or (
not parsed_url.scheme and not parsed_url.netloc):
raise InvalidRequirement("Invalid URL given")
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
self.url = req.url
else:
self.url = None

View File

@ -503,7 +503,7 @@ class Specifier(_IndividualSpecifier):
return False
# Ensure that we do not allow a local version of the version mentioned
# in the specifier, which is techincally greater than, to match.
# in the specifier, which is technically greater than, to match.
if prospective.local is not None:
if Version(prospective.base_version) == Version(spec.base_version):
return False

View File

@ -47,6 +47,11 @@ except ImportError:
# Python 3.2 compatibility
import imp as _imp
try:
FileExistsError
except NameError:
FileExistsError = OSError
from pip._vendor import six
from pip._vendor.six.moves import urllib, map, filter
@ -78,8 +83,11 @@ __import__('pip._vendor.packaging.requirements')
__import__('pip._vendor.packaging.markers')
if (3, 0) < sys.version_info < (3, 3):
raise RuntimeError("Python 3.3 or later is required")
__metaclass__ = type
if (3, 0) < sys.version_info < (3, 4):
raise RuntimeError("Python 3.4 or later is required")
if six.PY2:
# Those builtin exceptions are only defined in Python 3
@ -537,7 +545,7 @@ class IResourceProvider(IMetadataProvider):
"""List of resource names in the directory (like ``os.listdir()``)"""
class WorkingSet(object):
class WorkingSet:
"""A collection of active distributions on sys.path (or a similar list)"""
def __init__(self, entries=None):
@ -637,13 +645,12 @@ class WorkingSet(object):
distributions in the working set, otherwise only ones matching
both `group` and `name` are yielded (in distribution order).
"""
for dist in self:
entries = dist.get_entry_map(group)
if name is None:
for ep in entries.values():
yield ep
elif name in entries:
yield entries[name]
return (
entry
for dist in self
for entry in dist.get_entry_map(group).values()
if name is None or name == entry.name
)
def run_script(self, requires, script_name):
"""Locate distribution for `requires` and run `script_name` script"""
@ -944,7 +951,7 @@ class _ReqExtras(dict):
return not req.marker or any(extra_evals)
class Environment(object):
class Environment:
"""Searchable snapshot of distributions on a search path"""
def __init__(
@ -959,7 +966,7 @@ class Environment(object):
`platform` is an optional string specifying the name of the platform
that platform-specific distributions must be compatible with. If
unspecified, it defaults to the current platform. `python` is an
optional string naming the desired version of Python (e.g. ``'3.3'``);
optional string naming the desired version of Python (e.g. ``'3.6'``);
it defaults to the current version.
You may explicitly set `platform` (and/or `python`) to ``None`` if you
@ -2087,7 +2094,12 @@ def _handle_ns(packageName, path_item):
importer = get_importer(path_item)
if importer is None:
return None
loader = importer.find_module(packageName)
# capture warnings due to #1111
with warnings.catch_warnings():
warnings.simplefilter("ignore")
loader = importer.find_module(packageName)
if loader is None:
return None
module = sys.modules.get(packageName)
@ -2132,12 +2144,13 @@ def _rebuild_mod_path(orig_path, package_name, module):
parts = path_parts[:-module_parts]
return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
if not isinstance(orig_path, list):
# Is this behavior useful when module.__path__ is not a list?
return
new_path = sorted(orig_path, key=position_in_sys_path)
new_path = [_normalize_cached(p) for p in new_path]
orig_path.sort(key=position_in_sys_path)
module.__path__[:] = [_normalize_cached(p) for p in orig_path]
if isinstance(module.__path__, list):
module.__path__[:] = new_path
else:
module.__path__ = new_path
def declare_namespace(packageName):
@ -2148,9 +2161,10 @@ def declare_namespace(packageName):
if packageName in _namespace_packages:
return
path, parent = sys.path, None
if '.' in packageName:
parent = '.'.join(packageName.split('.')[:-1])
path = sys.path
parent, _, _ = packageName.rpartition('.')
if parent:
declare_namespace(parent)
if parent not in _namespace_packages:
__import__(parent)
@ -2161,7 +2175,7 @@ def declare_namespace(packageName):
# Track what packages are namespaces, so when new path items are added,
# they can be updated
_namespace_packages.setdefault(parent, []).append(packageName)
_namespace_packages.setdefault(parent or None, []).append(packageName)
_namespace_packages.setdefault(packageName, [])
for path_item in path:
@ -2279,7 +2293,7 @@ EGG_NAME = re.compile(
).match
class EntryPoint(object):
class EntryPoint:
"""Object representing an advertised importable object"""
def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
@ -2433,7 +2447,7 @@ def _version_from_file(lines):
return safe_version(value.strip()) or None
class Distribution(object):
class Distribution:
"""Wrap an actual or potential sys.path entry w/metadata"""
PKG_INFO = 'PKG-INFO'
@ -3027,7 +3041,10 @@ def _bypass_ensure_directory(path):
dirname, filename = split(path)
if dirname and filename and not isdir(dirname):
_bypass_ensure_directory(dirname)
mkdir(dirname, 0o755)
try:
mkdir(dirname, 0o755)
except FileExistsError:
pass
def split_sections(s):

View File

@ -2,6 +2,8 @@ import os
import errno
import sys
from pip._vendor import six
def _makedirs_31(path, exist_ok=False):
try:
@ -15,8 +17,7 @@ def _makedirs_31(path, exist_ok=False):
# and exists_ok considerations are disentangled.
# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663
needs_makedirs = (
sys.version_info < (3, 2, 5) or
(3, 3) <= sys.version_info < (3, 3, 6) or
six.PY2 or
(3, 4) <= sys.version_info < (3, 4, 1)
)
makedirs = _makedirs_31 if needs_makedirs else os.makedirs

View File

@ -1,6 +1,6 @@
# module pyparsing.py
#
# Copyright (c) 2003-2016 Paul T. McGuire
# Copyright (c) 2003-2018 Paul T. McGuire
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -25,6 +25,7 @@
__doc__ = \
"""
pyparsing module - Classes and methods to define and execute parsing grammars
=============================================================================
The pyparsing module is an alternative approach to creating and executing simple grammars,
vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you
@ -58,10 +59,23 @@ The pyparsing module handles some of the problems that are typically vexing when
- extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.)
- quoted strings
- embedded comments
Getting Started -
-----------------
Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
classes inherit from. Use the docstrings for examples of how to:
- construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
- construct character word-group expressions using the L{Word} class
- see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
- use L{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>} operators to combine simple expressions into more complex ones
- associate names with your parsed results using L{ParserElement.setResultsName}
- find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
- find more useful common expressions in the L{pyparsing_common} namespace class
"""
__version__ = "2.2.0"
__versionTime__ = "06 Mar 2017 02:06 UTC"
__version__ = "2.2.1"
__versionTime__ = "18 Sep 2018 00:49 UTC"
__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>"
import string
@ -82,6 +96,15 @@ try:
except ImportError:
from threading import RLock
try:
# Python 3
from collections.abc import Iterable
from collections.abc import MutableMapping
except ImportError:
# Python 2.7
from collections import Iterable
from collections import MutableMapping
try:
from collections import OrderedDict as _OrderedDict
except ImportError:
@ -940,7 +963,7 @@ class ParseResults(object):
def __dir__(self):
return (dir(type(self)) + list(self.keys()))
collections.MutableMapping.register(ParseResults)
MutableMapping.register(ParseResults)
def col (loc,strg):
"""Returns current column within a string, counting newlines as line separators.
@ -1025,11 +1048,11 @@ def _trim_arity(func, maxargs=2):
# special handling for Python 3.5.0 - extra deep call stack by 1
offset = -3 if system_version == (3,5,0) else -2
frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
return [(frame_summary.filename, frame_summary.lineno)]
return [frame_summary[:2]]
def extract_tb(tb, limit=0):
frames = traceback.extract_tb(tb, limit=limit)
frame_summary = frames[-1]
return [(frame_summary.filename, frame_summary.lineno)]
return [frame_summary[:2]]
else:
extract_stack = traceback.extract_stack
extract_tb = traceback.extract_tb
@ -1374,7 +1397,7 @@ class ParserElement(object):
else:
preloc = loc
tokensStart = preloc
if self.mayIndexError or loc >= len(instring):
if self.mayIndexError or preloc >= len(instring):
try:
loc,tokens = self.parseImpl( instring, preloc, doActions )
except IndexError:
@ -1408,7 +1431,6 @@ class ParserElement(object):
self.resultsName,
asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
modal=self.modalResults )
if debugging:
#~ print ("Matched",self,"->",retTokens.asList())
if (self.debugActions[1] ):
@ -3242,7 +3264,7 @@ class ParseExpression(ParserElement):
if isinstance( exprs, basestring ):
self.exprs = [ ParserElement._literalStringClass( exprs ) ]
elif isinstance( exprs, collections.Iterable ):
elif isinstance( exprs, Iterable ):
exprs = list(exprs)
# if sequence of strings provided, wrap with Literal
if all(isinstance(expr, basestring) for expr in exprs):
@ -4393,7 +4415,7 @@ def traceParseAction(f):
@traceParseAction
def remove_duplicate_chars(tokens):
return ''.join(sorted(set(''.join(tokens)))
return ''.join(sorted(set(''.join(tokens))))
wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
@ -4583,7 +4605,7 @@ def oneOf( strs, caseless=False, useRegex=True ):
symbols = []
if isinstance(strs,basestring):
symbols = strs.split()
elif isinstance(strs, collections.Iterable):
elif isinstance(strs, Iterable):
symbols = list(strs)
else:
warnings.warn("Invalid argument to oneOf, expected string or iterable",
@ -4734,7 +4756,7 @@ stringEnd = StringEnd().setName("stringEnd")
_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | Word(printables, excludeChars=r'\]', exact=1) | Regex(r"\w", re.UNICODE)
_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
_charRange = Group(_singleChar + Suppress("-") + _singleChar)
_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"

View File

@ -223,8 +223,8 @@ _float_re = re.compile(r'[+-]?(?:0|[1-9](?:_?\d)*)(?:\.\d(?:_?\d)*)?(?:[eE][+-]?
_datetime_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|([+-]\d{2}):(\d{2}))')
_basicstr_ml_re = re.compile(r'(?:(?:|"|"")[^"\\\000-\011\013-\037])*')
_litstr_re = re.compile(r"[^'\000-\037]*")
_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\011\013-\037]))*")
_litstr_re = re.compile(r"[^'\000\010\012-\037]*")
_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\010\013-\037]))*")
def _p_value(s, object_pairs_hook):
pos = s.pos()

View File

@ -9,15 +9,15 @@ msgpack-python==0.5.6
lockfile==0.12.2
progress==1.4
ipaddress==1.0.22 # Only needed on 2.6 and 2.7
packaging==17.1
packaging==18.0
pep517==0.2
pyparsing==2.2.0
pytoml==0.1.16
pyparsing==2.2.1
pytoml==0.1.19
retrying==1.3.3
requests==2.19.1
chardet==3.0.4
idna==2.7
urllib3==1.23
certifi==2018.4.16
setuptools==39.2.0
certifi==2018.8.24
setuptools==40.4.3
webencodings==0.5.1

View File

@ -75,7 +75,8 @@ def rewrite_file_imports(item, vendored_libs):
"""Rewrite 'import xxx' and 'from xxx import' for vendored_libs"""
text = item.read_text(encoding='utf-8')
# Revendor pkg_resources.extern first
text = re.sub(r'pkg_resources.extern', r'pip._vendor', text)
text = re.sub(r'pkg_resources\.extern', r'pip._vendor', text)
text = re.sub(r'from \.extern', r'from pip._vendor', text)
for lib in vendored_libs:
text = re.sub(
r'(\n\s*|^)import %s(\n\s*)' % lib,

View File

@ -59,10 +59,6 @@ parent/child-0.1.tar.gz
The parent-0.1.tar.gz and child-0.1.tar.gz packages are used by
test_uninstall:test_uninstall_overlapping_package.
paxpkg.tar.bz2
--------------
tar with pax headers
pkgwithmpkg-1.0.tar.gz; pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip
-----------------------------------------------------------------
used for osx test case (tests.test_finder:test_no_mpkg)

Binary file not shown.

View File

@ -1,6 +1,6 @@
<html><head><title>PyPi Mirror</title></head>
<html><head><title>PyPI Mirror</title></head>
<body>
<h1>PyPi Mirror</h1>
<h1>PyPI Mirror</h1>
<h2>For testing --index-url with a file:// url for the index</h2>
<a href='Dinner-1.0.tar.gz'>Dinner-1.0.tar.gz</a><br />
<a href='Dinner-2.0.tar.gz'>Dinner-2.0.tar.gz</a><br />

View File

@ -1,6 +1,6 @@
<html><head><title>PyPi Mirror</title></head>
<html><head><title>PyPI Mirror</title></head>
<body>
<h1>PyPi Mirror</h1>
<h1>PyPI Mirror</h1>
<h2>For testing --index-url with a file:// url for the index</h2>
<a href='requiredinner'>requiredinner</a><br />
<a href='Dinner'>Dinner</a><br />

View File

@ -1,6 +1,6 @@
<html><head><title>PyPi Mirror</title></head>
<html><head><title>PyPI Mirror</title></head>
<body>
<h1>PyPi Mirror</h1>
<h1>PyPI Mirror</h1>
<h2>For testing --index-url with a file:// url for the index</h2>
<a href='requiredinner-1.0.tar.gz'>requiredinner=1.0.tar.gz</a><br />
</body>

View File

@ -204,6 +204,30 @@ def test_completion_not_files_after_option(script, data):
)
@pytest.mark.parametrize("cl_opts", ["-U", "--user", "-h"])
def test_completion_not_files_after_nonexpecting_option(script, data, cl_opts):
"""
Test not getting completion files after options which not applicable
(e.g. ``pip install``)
"""
res, env = setup_completion(
script=script,
words=('pip install %s r' % cl_opts),
cword='2',
cwd=data.completion_paths,
)
assert not any(out in res.stdout for out in
('requirements.txt', 'readme.txt',)), (
"autocomplete function completed <file> when "
"it should not complete"
)
assert not any(os.path.join(out, '') in res.stdout
for out in ('replay', 'resources')), (
"autocomplete function completed <dir> when "
"it should not complete"
)
def test_completion_directories_after_option(script, data):
"""
Test getting completion <dir> after options in command

View File

@ -57,7 +57,7 @@ def test_download_wheel(script, data):
@pytest.mark.network
def test_single_download_from_requirements_file(script):
"""
It should support download (in the scratch path) from PyPi from a
It should support download (in the scratch path) from PyPI from a
requirements file
"""
script.scratch_path.join("test-req.txt").write(textwrap.dedent("""

View File

@ -522,13 +522,6 @@ def test_install_global_option(script):
assert '0.1\n' in result.stdout
def test_install_with_pax_header(script, data):
"""
test installing from a tarball with pax header for python<2.6
"""
script.pip('install', 'paxpkg.tar.bz2', cwd=data.packages)
def test_install_with_hacked_egg_info(script, data):
"""
test installing a package which defines its own egg_info class
@ -629,7 +622,7 @@ def test_install_folder_using_relative_path(script):
@pytest.mark.network
def test_install_package_which_contains_dev_in_name(script):
"""
Test installing package from pypi which contains 'dev' in name
Test installing package from PyPI which contains 'dev' in name
"""
result = script.pip('install', 'django-devserver==0.0.4')
devserver_folder = script.site_packages / 'devserver'

View File

@ -10,15 +10,32 @@ from tests.lib.git_submodule_helpers import (
from tests.lib.local_repos import local_checkout
def _get_editable_repo_dir(script, package_name):
"""
Return the repository directory for an editable install.
"""
return script.venv_path / 'src' / package_name
def _get_editable_branch(script, package_name):
"""
Return the current branch of an editable install.
"""
repo_dir = script.venv_path / 'src' / package_name
repo_dir = _get_editable_repo_dir(script, package_name)
result = script.run(
'git', 'rev-parse', '--abbrev-ref', 'HEAD', cwd=repo_dir
)
return result.stdout.strip()
def _get_branch_remote(script, package_name, branch):
"""
"""
repo_dir = _get_editable_repo_dir(script, package_name)
result = script.run(
'git', 'config', 'branch.{}.remote'.format(branch), cwd=repo_dir
)
return result.stdout.strip()
@ -363,7 +380,69 @@ def test_git_works_with_editable_non_origin_repo(script):
assert "version-pkg==0.1" in result.stdout
def test_editable_non_master_default_branch(script):
def test_editable__no_revision(script):
"""
Test a basic install in editable mode specifying no revision.
"""
version_pkg_path = _create_test_package(script)
_install_version_pkg_only(script, version_pkg_path)
branch = _get_editable_branch(script, 'version-pkg')
assert branch == 'master'
remote = _get_branch_remote(script, 'version-pkg', 'master')
assert remote == 'origin'
def test_editable__branch_with_sha_same_as_default(script):
"""
Test installing in editable mode a branch whose sha matches the sha
of the default branch, but is different from the default branch.
"""
version_pkg_path = _create_test_package(script)
# Create a second branch with the same SHA.
script.run(
'git', 'branch', 'develop', expect_stderr=True,
cwd=version_pkg_path,
)
_install_version_pkg_only(
script, version_pkg_path, rev='develop', expect_stderr=True
)
branch = _get_editable_branch(script, 'version-pkg')
assert branch == 'develop'
remote = _get_branch_remote(script, 'version-pkg', 'develop')
assert remote == 'origin'
def test_editable__branch_with_sha_different_from_default(script):
"""
Test installing in editable mode a branch whose sha is different from
the sha of the default branch.
"""
version_pkg_path = _create_test_package(script)
# Create a second branch.
script.run(
'git', 'branch', 'develop', expect_stderr=True,
cwd=version_pkg_path,
)
# Add another commit to the master branch to give it a different sha.
_change_test_package_version(script, version_pkg_path)
version = _install_version_pkg(
script, version_pkg_path, rev='develop', expect_stderr=True
)
assert version == '0.1'
branch = _get_editable_branch(script, 'version-pkg')
assert branch == 'develop'
remote = _get_branch_remote(script, 'version-pkg', 'develop')
assert remote == 'origin'
def test_editable__non_master_default_branch(script):
"""
Test the branch you get after an editable install from a remote repo
with a non-master default branch.
@ -376,8 +455,9 @@ def test_editable_non_master_default_branch(script):
cwd=version_pkg_path,
)
_install_version_pkg_only(script, version_pkg_path)
branch = _get_editable_branch(script, 'version-pkg')
assert 'release' == branch
assert branch == 'release'
def test_reinstalling_works_with_editable_non_master_branch(script):

View File

@ -25,7 +25,7 @@ def test_version_compare():
def test_pypi_xml_transformation():
"""
Test transformation of data structures (pypi xmlrpc to custom list).
Test transformation of data structures (PyPI xmlrpc to custom list).
"""
pypi_hits = [

View File

@ -37,9 +37,9 @@ def add_commits(script, dest, count):
return shas
def check_rev(repo_dir, rev, expected_sha):
def check_rev(repo_dir, rev, expected):
git = Git()
assert git.get_revision_sha(repo_dir, rev) == expected_sha
assert git.get_revision_sha(repo_dir, rev) == expected
def test_git_dir_ignored():
@ -70,6 +70,27 @@ def test_git_work_tree_ignored():
git.run_command(['status', temp_dir], extra_environ=env, cwd=temp_dir)
def test_get_branch(script, tmpdir):
repo_dir = str(tmpdir)
script.run('git', 'init', cwd=repo_dir)
sha = do_commit(script, repo_dir)
git = Git()
assert git.get_branch(repo_dir) == 'master'
# Switch to a branch with the same SHA as "master" but whose name
# is alphabetically after.
script.run(
'git', 'checkout', '-b', 'release', cwd=repo_dir,
expect_stderr=True,
)
assert git.get_branch(repo_dir) == 'release'
# Also test the detached HEAD case.
script.run('git', 'checkout', sha, cwd=repo_dir, expect_stderr=True)
assert git.get_branch(repo_dir) is None
def test_get_revision_sha(script):
with TempDirectory(kind="testing") as temp:
repo_dir = temp.path
@ -102,9 +123,9 @@ def test_get_revision_sha(script):
script.run('git', 'tag', 'aaa/v1.0', head_sha, cwd=repo_dir)
script.run('git', 'tag', 'zzz/v1.0', head_sha, cwd=repo_dir)
check_rev(repo_dir, 'v1.0', tag_sha)
check_rev(repo_dir, 'v2.0', tag_sha)
check_rev(repo_dir, 'origin-branch', origin_sha)
check_rev(repo_dir, 'v1.0', (tag_sha, False))
check_rev(repo_dir, 'v2.0', (tag_sha, False))
check_rev(repo_dir, 'origin-branch', (origin_sha, True))
ignored_names = [
# Local branches should be ignored.
@ -122,7 +143,7 @@ def test_get_revision_sha(script):
'does-not-exist',
]
for name in ignored_names:
check_rev(repo_dir, name, None)
check_rev(repo_dir, name, (None, False))
@pytest.mark.network

View File

@ -34,13 +34,13 @@ class ConfigurationMixin(object):
old = self.configuration._load_config_files
@functools.wraps(old)
def overidden():
def overridden():
# Manual Overload
self.configuration._config[variant].update(di)
self.configuration._parsers[variant].append((None, None))
return old()
self.configuration._load_config_files = overidden
self.configuration._load_config_files = overridden
@contextlib.contextmanager
def tmpfile(self, contents):

View File

@ -68,6 +68,23 @@ class TestConfigurationLoading(ConfigurationMixin):
with pytest.raises(ConfigurationError):
self.configuration.get_value(":env:.version")
def test_environment_config_errors_if_malformed(self):
contents = """
test]
hello = 4
"""
with self.tmpfile(contents) as config_file:
os.environ["PIP_CONFIG_FILE"] = config_file
with pytest.raises(ConfigurationError) as err:
self.configuration.load()
assert "section header" in str(err.value) # error kind
assert "1" in str(err.value) # line number
assert ( # file name
config_file in str(err.value) or
repr(config_file) in str(err.value)
)
class TestConfigurationPrecedence(ConfigurationMixin):
# Tests for methods to that determine the order of precedence of

View File

@ -95,7 +95,7 @@ def test_finder_detects_latest_already_satisfied_find_links(data):
def test_finder_detects_latest_already_satisfied_pypi_links():
"""Test PackageFinder detects latest already satisfied using pypi links"""
req = install_req_from_line('initools', None)
# the latest initools on pypi is 0.3.1
# the latest initools on PyPI is 0.3.1
latest_version = "0.3.1"
satisfied_by = Mock(
location="/path",

View File

@ -1,9 +1,14 @@
import logging
import os.path
import pytest
from mock import Mock
from pip._vendor import html5lib, requests
from pip._internal.download import PipSession
from pip._internal.index import HTMLPage, Link, PackageFinder
from pip._internal.index import (
Link, PackageFinder, _determine_base_url, _get_html_page, egg_info_matches,
)
def test_sort_locations_file_expand_dir(data):
@ -107,8 +112,11 @@ class TestLink(object):
),
],
)
def test_base_url(html, url, expected):
assert HTMLPage(html, url).base_url == expected
def test_determine_base_url(html, url, expected):
document = html5lib.parse(
html, transport_encoding=None, namespaceHTMLElements=False,
)
assert _determine_base_url(document, url) == expected
class MockLogger(object):
@ -154,3 +162,58 @@ def test_get_formatted_locations_basic_auth():
result = finder.get_formatted_locations()
assert 'user' not in result and 'pass' not in result
@pytest.mark.parametrize(
("egg_info", "search_name", "expected"),
[
# Trivial.
("pip-18.0", "pip", "18.0"),
("pip-18.0", None, "18.0"),
# Non-canonical names.
("Jinja2-2.10", "jinja2", "2.10"),
("jinja2-2.10", "Jinja2", "2.10"),
# Ambiguous names. Should be smart enough if the package name is
# provided, otherwise make a guess.
("foo-2-2", "foo", "2-2"),
("foo-2-2", "foo-2", "2"),
("foo-2-2", None, "2-2"),
("im-valid", None, "valid"),
# Invalid names.
("invalid", None, None),
("im_invalid", None, None),
("the-package-name-8.19", "does-not-match", None),
],
)
def test_egg_info_matches(egg_info, search_name, expected):
link = None # Only used for reporting.
version = egg_info_matches(egg_info, search_name, link)
assert version == expected
def test_request_http_error(caplog):
caplog.set_level(logging.DEBUG)
link = Link('http://localhost')
session = Mock(PipSession)
session.get.return_value = resp = Mock()
resp.raise_for_status.side_effect = requests.HTTPError('Http error')
assert _get_html_page(link, session=session) is None
assert (
'Could not fetch URL http://localhost: Http error - skipping'
in caplog.text
)
def test_request_retries(caplog):
caplog.set_level(logging.DEBUG)
link = Link('http://localhost')
session = Mock(PipSession)
session.get.side_effect = requests.exceptions.RetryError('Retry error')
assert _get_html_page(link, session=session) is None
assert (
'Could not fetch URL http://localhost: Retry error - skipping'
in caplog.text
)

View File

@ -0,0 +1,162 @@
import logging
import mock
import pytest
from pip._vendor.six.moves.urllib import request as urllib_request
from pip._internal.download import PipSession
from pip._internal.index import (
Link, _get_html_page, _get_html_response, _NotHTML, _NotHTTP,
)
@pytest.mark.parametrize(
"url",
[
"ftp://python.org/python-3.7.1.zip",
"file:///opt/data/pip-18.0.tar.gz",
],
)
def test_get_html_response_archive_to_naive_scheme(url):
"""
`_get_html_response()` should error on an archive-like URL if the scheme
does not allow "poking" without getting data.
"""
with pytest.raises(_NotHTTP):
_get_html_response(url, session=mock.Mock(PipSession))
@pytest.mark.parametrize(
"url, content_type",
[
("http://python.org/python-3.7.1.zip", "application/zip"),
("https://pypi.org/pip-18.0.tar.gz", "application/gzip"),
],
)
def test_get_html_response_archive_to_http_scheme(url, content_type):
"""
`_get_html_response()` should send a HEAD request on an archive-like URL
if the scheme supports it, and raise `_NotHTML` if the response isn't HTML.
"""
session = mock.Mock(PipSession)
session.head.return_value = mock.Mock(**{
"request.method": "HEAD",
"headers": {"Content-Type": content_type},
})
with pytest.raises(_NotHTML) as ctx:
_get_html_response(url, session=session)
session.assert_has_calls([
mock.call.head(url, allow_redirects=True),
])
assert ctx.value.args == (content_type, "HEAD")
@pytest.mark.parametrize(
"url",
[
"http://python.org/python-3.7.1.zip",
"https://pypi.org/pip-18.0.tar.gz",
],
)
def test_get_html_response_archive_to_http_scheme_is_html(url):
"""
`_get_html_response()` should work with archive-like URLs if the HEAD
request is responded with text/html.
"""
session = mock.Mock(PipSession)
session.head.return_value = mock.Mock(**{
"request.method": "HEAD",
"headers": {"Content-Type": "text/html"},
})
session.get.return_value = mock.Mock(headers={"Content-Type": "text/html"})
resp = _get_html_response(url, session=session)
assert resp is not None
assert session.mock_calls == [
mock.call.head(url, allow_redirects=True),
mock.call.head().raise_for_status(),
mock.call.get(url, headers={
"Accept": "text/html", "Cache-Control": "max-age=0",
}),
mock.call.get().raise_for_status(),
]
@pytest.mark.parametrize(
"url",
[
"https://pypi.org/simple/pip",
"https://pypi.org/simple/pip/",
"https://python.org/sitemap.xml",
],
)
def test_get_html_response_no_head(url):
"""
`_get_html_response()` shouldn't send a HEAD request if the URL does not
look like an archive, only the GET request that retrieves data.
"""
session = mock.Mock(PipSession)
# Mock the headers dict to ensure it is accessed.
session.get.return_value = mock.Mock(headers=mock.Mock(**{
"get.return_value": "text/html",
}))
resp = _get_html_response(url, session=session)
assert resp is not None
assert session.head.call_count == 0
assert session.get.mock_calls == [
mock.call(url, headers={
"Accept": "text/html", "Cache-Control": "max-age=0",
}),
mock.call().raise_for_status(),
mock.call().headers.get("Content-Type", ""),
]
@pytest.mark.parametrize(
"url, vcs_scheme",
[
("svn+http://pypi.org/something", "svn"),
("git+https://github.com/pypa/pip.git", "git"),
],
)
def test_get_html_page_invalid_scheme(caplog, url, vcs_scheme):
"""`_get_html_page()` should error if an invalid scheme is given.
Only file:, http:, https:, and ftp: are allowed.
"""
with caplog.at_level(logging.DEBUG):
page = _get_html_page(Link(url), session=mock.Mock(PipSession))
assert page is None
assert caplog.record_tuples == [
(
"pip._internal.index",
logging.DEBUG,
"Cannot look at {} URL {}".format(vcs_scheme, url),
),
]
def test_get_html_page_directory_append_index(tmpdir):
"""`_get_html_page()` should append "index.html" to a directory URL.
"""
dirpath = tmpdir.mkdir("something")
dir_url = "file:///{}".format(
urllib_request.pathname2url(dirpath).lstrip("/"),
)
session = mock.Mock(PipSession)
with mock.patch("pip._internal.index._get_html_response") as mock_func:
_get_html_page(Link(dir_url), session=session)
assert mock_func.mock_calls == [
mock.call(
"{}/index.html".format(dir_url.rstrip("/")),
session=session,
),
]

View File

@ -66,7 +66,8 @@ class TestRequirementSet(object):
build_dir = os.path.join(self.tempdir, 'build', 'simple')
os.makedirs(build_dir)
open(os.path.join(build_dir, "setup.py"), 'w')
with open(os.path.join(build_dir, "setup.py"), 'w'):
pass
reqset = RequirementSet()
req = install_req_from_line('simple')
req.is_direct = True

View File

@ -24,8 +24,8 @@ from pip._internal.utils.glibc import check_glibc_version
from pip._internal.utils.hashes import Hashes, MissingHashes
from pip._internal.utils.misc import (
call_subprocess, egg_link_path, ensure_dir, get_installed_distributions,
get_prog, normalize_path, remove_auth_from_url, rmtree,
split_auth_from_netloc, untar_file, unzip_file,
get_prog, make_vcs_requirement_url, normalize_path, remove_auth_from_url,
rmtree, split_auth_from_netloc, untar_file, unzip_file,
)
from pip._internal.utils.packaging import check_dist_requires_python
from pip._internal.utils.temp_dir import TempDirectory
@ -627,6 +627,22 @@ def test_call_subprocess_closes_stdin():
call_subprocess([sys.executable, '-c', 'input()'])
@pytest.mark.parametrize('args, expected', [
# Test without subdir.
(('git+https://example.com/pkg', 'dev', 'myproj'),
'git+https://example.com/pkg@dev#egg=myproj'),
# Test with subdir.
(('git+https://example.com/pkg', 'dev', 'myproj', 'sub/dir'),
'git+https://example.com/pkg@dev#egg=myproj&subdirectory=sub/dir'),
# Test with None subdir.
(('git+https://example.com/pkg', 'dev', 'myproj', None),
'git+https://example.com/pkg@dev#egg=myproj'),
])
def test_make_vcs_requirement_url(args, expected):
actual = make_vcs_requirement_url(*args)
assert actual == expected
@pytest.mark.parametrize('netloc, expected', [
# Test a basic case.
('example.com', ('example.com', (None, None))),

View File

@ -109,7 +109,7 @@ def test_git_get_src_requirements(git, dist):
@patch('pip._internal.vcs.git.Git.get_revision_sha')
def test_git_resolve_revision_rev_exists(get_sha_mock):
get_sha_mock.return_value = '123456'
get_sha_mock.return_value = ('123456', False)
git = Git()
rev_options = git.make_rev_options('develop')
@ -120,7 +120,7 @@ def test_git_resolve_revision_rev_exists(get_sha_mock):
@patch('pip._internal.vcs.git.Git.get_revision_sha')
def test_git_resolve_revision_rev_not_found(get_sha_mock):
get_sha_mock.return_value = None
get_sha_mock.return_value = (None, False)
git = Git()
rev_options = git.make_rev_options('develop')
@ -131,7 +131,7 @@ def test_git_resolve_revision_rev_not_found(get_sha_mock):
@patch('pip._internal.vcs.git.Git.get_revision_sha')
def test_git_resolve_revision_not_found_warning(get_sha_mock, caplog):
get_sha_mock.return_value = None
get_sha_mock.return_value = (None, False)
git = Git()
url = 'git+https://git.example.com'

View File

@ -0,0 +1 @@
mypy == 0.620

28
tools/tox_pip.py Normal file
View File

@ -0,0 +1,28 @@
import os
import shutil
import subprocess
import sys
from glob import glob
VIRTUAL_ENV = os.environ['VIRTUAL_ENV']
TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip')
def pip(args):
# First things first, get a recent (stable) version of pip.
if not os.path.exists(TOX_PIP_DIR):
subprocess.check_call([sys.executable, '-m', 'pip',
'--disable-pip-version-check',
'install', '-t', TOX_PIP_DIR,
'pip'])
shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0])
# And use that version.
pypath = os.environ.get('PYTHONPATH')
pypath = pypath.split(os.pathsep) if pypath is not None else []
pypath.insert(0, TOX_PIP_DIR)
os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
subprocess.check_call([sys.executable, '-m', 'pip'] + args)
if __name__ == '__main__':
pip(sys.argv[1:])

10
tox.ini
View File

@ -3,6 +3,11 @@ envlist =
docs, packaging, lint-py2, lint-py3, mypy,
py27, py34, py35, py36, py37, py38, pypy, pypy3
[helpers]
# Wrapper for calls to pip that make sure the version being used is the
# original virtualenv (stable) version, and not the code being tested.
pip = python {toxinidir}/tools/tox_pip.py
[testenv]
passenv = CI GIT_SSL_CAINFO
setenv =
@ -11,7 +16,8 @@ setenv =
LC_CTYPE = en_US.UTF-8
deps = -r{toxinidir}/tools/tests-requirements.txt
commands = pytest --timeout 300 []
install_command = python -m pip install {opts} {packages}
install_command = {[helpers]pip} install {opts} {packages}
list_dependencies_command = {[helpers]pip} freeze --all
usedevelop = True
[testenv:coverage-py3]
@ -60,7 +66,7 @@ commands = {[lint]commands}
[testenv:mypy]
skip_install = True
basepython = python3
deps = mypy
deps = -r{toxinidir}/tools/mypy-requirements.txt
commands =
mypy src
mypy src -2