diff --git a/.fmf/version b/.fmf/version new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/.gitignore b/.gitignore index 32022db..72d39d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,7 @@ -pip-0.7.2.tar.gz -/pip-0.8.tar.gz -/pip-0.8.2.tar.gz -/pip-0.8.3.tar.gz -/pip-1.0.2.tar.gz -/pip-1.1.tar.gz -/pip-1.3.1.tar.gz -/pip-1.4.1.tar.gz -/pip-1.5.4.tar.gz -/pip-1.5.6.tar.gz -/pip-1.5.6-tests.tar.gz -/pip-6.0.8.tar.gz -/pip-7.0.3.tar.gz -/pip-7.1.0.tar.gz -/pip-7.1.0-tests.tar.gz -/pip-8.0.2.tar.gz -/pip-8.1.2.tar.gz -/pip-8.1.2-tests.tar.gz -/pip-9.0.1.tar.gz -/pip-9.0.1-tests.tar.gz -/pip-9.0.3.tar.gz -/pip-9.0.3-tests.tar.gz -/pip-10.0.1.tar.gz -/pip-18.0.tar.gz -/d2e63fbfc62af3b7050f619b2f5bb8658985b931.zip -/2018.2.tar.gz -/pip-18.0-tests.tar.gz -/d2e63fbfc62af3b7050f619b2f5bb8658985b931.tar.gz -/pip-18.1.tar.gz -/pip-18.1-tests.tar.gz -/pip-19.0.2.tar.gz -/pip-19.0.2-tests.tar.gz -/pip-19.0.3.tar.gz -/pip-19.0.3-tests.tar.gz +/*.tar.gz +/*.zip +/pip-*/ +/pip/ +/results_python-pip/ +*.rpm +*.whl diff --git a/7873.patch b/7873.patch deleted file mode 100644 index 7008224..0000000 --- a/7873.patch +++ /dev/null @@ -1,167 +0,0 @@ -From e65d560c1c92278085510771a7f696d804081948 Mon Sep 17 00:00:00 2001 -From: Tomas Hrnciar -Date: Mon, 20 Apr 2020 14:14:05 +0200 -Subject: [PATCH 1/2] Backport of necessary changes from PR #6770, needed for - backport of PR #7872 - -https://github.com/pypa/pip/pull/6770 ---- - src/pip/_internal/download.py | 13 ++++++++++++- - 1 file changed, 12 insertions(+), 1 deletion(-) - -diff --git a/src/pip/_internal/download.py b/src/pip/_internal/download.py -index 2bbe1762..b3bd2414 100644 ---- a/src/pip/_internal/download.py -+++ b/src/pip/_internal/download.py -@@ -729,9 +729,20 @@ def unpack_file_url( - - # If it's a url to a local directory - if is_dir_url(link): -+ -+ def ignore(d, names): -+ # Pulling in those directories can potentially be very slow, -+ # exclude the following directories if they appear in the top -+ # level dir (and only it). -+ # See discussion at https://github.com/pypa/pip/pull/6770 -+ return ['.tox', '.nox'] if d == link_path else [] - if os.path.isdir(location): - rmtree(location) -- shutil.copytree(link_path, location, symlinks=True) -+ shutil.copytree(link_path, -+ location, -+ symlinks=True, -+ ignore=ignore) -+ - if download_dir: - logger.info('Link is a directory, ignoring download_dir') - return --- -2.23.0 - - -From f4ee6c36a8b37b1a325d2e2d47d6b3684ca5566b Mon Sep 17 00:00:00 2001 -From: Tomas Hrnciar -Date: Mon, 20 Apr 2020 14:46:49 +0200 -Subject: [PATCH 2/2] Prevent infinite recursion with pip wheel with $TMPDIR in - $PWD - -During a build of extension module within `pip wheel` the source directory is -recursively copied in a temporary directory. - -See https://github.com/pypa/pip/issues/7555 - -When the temporary directory is inside the source directory -(for example by setting `TMPDIR=$PWD/tmp`) this caused an infinite recursion -that ended in: - -[Errno 36] File name too long - -We prevent that buy never copying the target to the target in _copy_source_tree. - -Fixes https://github.com/pypa/pip/issues/7872 - -Avoid a test dependency on a C compiler, skip the test on Windows ---- - news/7872.bugfix | 1 + - src/pip/_internal/download.py | 23 ++++++++++++++++++----- - tests/data/src/extension/extension.c | 0 - tests/data/src/extension/setup.py | 4 ++++ - tests/functional/test_wheel.py | 17 +++++++++++++++++ - 5 files changed, 40 insertions(+), 5 deletions(-) - create mode 100644 news/7872.bugfix - create mode 100644 tests/data/src/extension/extension.c - create mode 100644 tests/data/src/extension/setup.py - -diff --git a/news/7872.bugfix b/news/7872.bugfix -new file mode 100644 -index 00000000..3550d573 ---- /dev/null -+++ b/news/7872.bugfix -@@ -0,0 +1 @@ -+Prevent an infinite recursion with ``pip wheel`` when ``$TMPDIR`` is within the source directory. -diff --git a/src/pip/_internal/download.py b/src/pip/_internal/download.py -index b3bd2414..80689012 100644 ---- a/src/pip/_internal/download.py -+++ b/src/pip/_internal/download.py -@@ -729,13 +729,26 @@ def unpack_file_url( - - # If it's a url to a local directory - if is_dir_url(link): -+ target_abspath = os.path.abspath(location) -+ target_basename = os.path.basename(target_abspath) -+ target_dirname = os.path.dirname(target_abspath) - - def ignore(d, names): -- # Pulling in those directories can potentially be very slow, -- # exclude the following directories if they appear in the top -- # level dir (and only it). -- # See discussion at https://github.com/pypa/pip/pull/6770 -- return ['.tox', '.nox'] if d == link_path else [] -+ # type: (str, List[str]) -> List[str] -+ skipped = [] # type: List[str] -+ if d == link_path: -+ # Pulling in those directories can potentially be very slow, -+ # exclude the following directories if they appear in the top -+ # level dir (and only it). -+ # See discussion at https://github.com/pypa/pip/pull/6770 -+ skipped += ['.tox', '.nox'] -+ if os.path.abspath(d) == target_dirname: -+ # Prevent an infinite recursion if the target is in source. -+ # This can happen when TMPDIR is set to ${PWD}/... -+ # and we copy PWD to TMPDIR. -+ skipped += [target_basename] -+ return skipped -+ - if os.path.isdir(location): - rmtree(location) - shutil.copytree(link_path, -diff --git a/tests/data/src/extension/extension.c b/tests/data/src/extension/extension.c -new file mode 100644 -index 00000000..e69de29b -diff --git a/tests/data/src/extension/setup.py b/tests/data/src/extension/setup.py -new file mode 100644 -index 00000000..b26302b0 ---- /dev/null -+++ b/tests/data/src/extension/setup.py -@@ -0,0 +1,4 @@ -+from setuptools import Extension, setup -+ -+module = Extension('extension', sources=['extension.c']) -+setup(name='extension', version='0.0.1', ext_modules = [module]) -diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py -index 7ab76c13..2a418bf4 100644 ---- a/tests/functional/test_wheel.py -+++ b/tests/functional/test_wheel.py -@@ -1,5 +1,6 @@ - """'pip wheel' tests""" - import os -+import sys - from os.path import exists - - import pytest -@@ -218,6 +219,22 @@ def test_pip_wheel_with_user_set_in_config(script, data, common_wheels): - ) - assert "Successfully built withpyproject" in result.stdout, result.stdout - -+@pytest.mark.skipif(sys.platform.startswith('win'), -+ reason='The empty extension module does not work on Win') -+def test_pip_wheel_ext_module_with_tmpdir_inside(script, data, common_wheels): -+ tmpdir = data.src / 'extension/tmp' -+ tmpdir.mkdir() -+ script.environ['TMPDIR'] = str(tmpdir) -+ -+ # To avoid a test dependency on a C compiler, we set the env vars to "noop" -+ # The .c source is empty anyway -+ script.environ['CC'] = script.environ['LDSHARED'] = str('true') -+ -+ result = script.pip( -+ 'wheel', data.src / 'extension', -+ '--no-index', '-f', common_wheels -+ ) -+ assert "Successfully built extension" in result.stdout, result.stdout - - def test_pep517_wheels_are_not_confused_with_other_files(script, tmpdir, data): - """Check correct wheels are copied. (#6196) --- -2.23.0 - diff --git a/allow-stripping-given-prefix-from-wheel-RECORD-files.patch b/allow-stripping-given-prefix-from-wheel-RECORD-files.patch deleted file mode 100644 index 8bf2735..0000000 --- a/allow-stripping-given-prefix-from-wheel-RECORD-files.patch +++ /dev/null @@ -1,110 +0,0 @@ -diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py -index 1c244d23..4b07ec0f 100644 ---- a/src/pip/_internal/commands/install.py -+++ b/src/pip/_internal/commands/install.py -@@ -109,6 +109,14 @@ class InstallCommand(RequirementCommand): - default=None, - help="Installation prefix where lib, bin and other top-level " - "folders are placed") -+ cmd_opts.add_option( -+ '--strip-file-prefix', -+ dest='strip_file_prefix', -+ metavar='prefix', -+ default=None, -+ help="Strip given prefix from script paths in wheel RECORD." -+ ) -+ - - cmd_opts.add_option(cmdoptions.build_dir()) - -@@ -391,6 +399,7 @@ class InstallCommand(RequirementCommand): - pycompile=options.compile, - warn_script_location=warn_script_location, - use_user_site=options.use_user_site, -+ strip_file_prefix=options.strip_file_prefix, - ) - - lib_locations = get_lib_location_guesses( -diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py -index a4834b00..d21530ac 100644 ---- a/src/pip/_internal/req/req_install.py -+++ b/src/pip/_internal/req/req_install.py -@@ -431,7 +431,8 @@ class InstallRequirement(object): - prefix=None, # type: Optional[str] - warn_script_location=True, # type: bool - use_user_site=False, # type: bool -- pycompile=True # type: bool -+ pycompile=True, # type: bool -+ strip_file_prefix=None # type: Optional[str] - ): - # type: (...) -> None - move_wheel_files( -@@ -443,6 +444,7 @@ class InstallRequirement(object): - pycompile=pycompile, - isolated=self.isolated, - warn_script_location=warn_script_location, -+ strip_file_prefix=strip_file_prefix, - ) - - # Things valid for sdists -@@ -894,7 +896,8 @@ class InstallRequirement(object): - prefix=None, # type: Optional[str] - warn_script_location=True, # type: bool - use_user_site=False, # type: bool -- pycompile=True # type: bool -+ pycompile=True, # type: bool -+ strip_file_prefix=None # type: Optional[str] - ): - # type: (...) -> None - global_options = global_options if global_options is not None else [] -@@ -911,6 +914,7 @@ class InstallRequirement(object): - self.source_dir, root=root, prefix=prefix, home=home, - warn_script_location=warn_script_location, - use_user_site=use_user_site, pycompile=pycompile, -+ strip_file_prefix=strip_file_prefix, - ) - self.install_succeeded = True - return -diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py -index 67bcc7f7..6470576b 100644 ---- a/src/pip/_internal/wheel.py -+++ b/src/pip/_internal/wheel.py -@@ -265,6 +265,7 @@ def get_csv_rows_for_installed( - changed, # type: set - generated, # type: List[str] - lib_dir, # type: str -+ strip_file_prefix=None, # type: Optional[str] - ): - # type: (...) -> List[InstalledCSVRow] - """ -@@ -289,7 +290,11 @@ def get_csv_rows_for_installed( - installed_rows.append(tuple(row)) - for f in generated: - digest, length = rehash(f) -- installed_rows.append((normpath(f, lib_dir), digest, str(length))) -+ final_path = normpath(f, lib_dir) -+ if strip_file_prefix and final_path.startswith(strip_file_prefix): -+ final_path = os.path.join(os.sep, -+ os.path.relpath(final_path, strip_file_prefix)) -+ installed_rows.append((final_path, digest, str(length))) - for f in installed: - installed_rows.append((installed[f], '', '')) - return installed_rows -@@ -306,7 +311,8 @@ def move_wheel_files( - scheme=None, # type: Optional[Mapping[str, str]] - isolated=False, # type: bool - prefix=None, # type: Optional[str] -- warn_script_location=True # type: bool -+ warn_script_location=True, # type: bool -+ strip_file_prefix=None # type: Optional[str] - ): - # type: (...) -> None - """Install a wheel""" -@@ -605,6 +611,7 @@ if __name__ == '__main__': - outrows = get_csv_rows_for_installed( - reader, installed=installed, changed=changed, - generated=generated, lib_dir=lib_dir, -+ strip_file_prefix=strip_file_prefix - ) - writer = csv.writer(record_out) - # Sort to simplify testing. diff --git a/changelog b/changelog new file mode 100644 index 0000000..f16cb91 --- /dev/null +++ b/changelog @@ -0,0 +1,496 @@ +* Mon Jan 22 2024 Miro Hrončok - 23.3.1-5 +- Switched to autogenerated BuildRequires for test dependencies, + which removed some that were no longer necessary + +* Mon Jan 22 2024 Fedora Release Engineering - 23.3.1-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jan 12 2024 Maxwell G - 23.3.1-3 +- Remove unused python3-mock dependency + +* Wed Jan 03 2024 Maxwell G - 23.3.1-2 +- Remove weak dependency on python3-setuptools + +* Thu Nov 16 2023 Petr Viktorin - 23.3.1-1 +- Update to 23.3.1 +Resolves: rhbz#2244306 + +* Fri Aug 04 2023 Miro Hrončok - 23.2.1-1 +- Update to 23.2.1 +Resolves: rhbz#2223082 + +* Fri Aug 04 2023 Miro Hrončok - 23.1.2-7 +- Actually run the tests and build the docs when building this package + +* Wed Jul 26 2023 Miro Hrončok - 23.1.2-6 +- Drop no-longer-needed custom changes to /usr/bin/pip* +- Stop Recommending libcrypt.so.1 on Python 3.12+ +Resolves: rhbz#2150373 + +* Tue Jul 25 2023 Python Maint - 23.1.2-5 +- Rebuilt for Python 3.12 + +* Fri Jul 21 2023 Fedora Release Engineering - 23.1.2-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Thu Jul 20 2023 Python Maint - 23.1.2-3 +- Rebuilt for Python 3.12 + +* Tue Jun 13 2023 Python Maint - 23.1.2-2 +- Bootstrap for Python 3.12 + +* Fri May 19 2023 Miro Hrončok - 23.1.2-1 +- Update to 23.1.2 +Resolves: rhbz#2186979 + +* Mon Mar 27 2023 Karolina Surma - 23.0.1-2 +- Fix compatibility with Sphinx 6+ +Resolves: rhbz#2180479 + +* Mon Feb 20 2023 Tomáš Hrnčiar - 23.0.1-1 +- Update to 23.0.1 +Resolves: rhbz#2165760 + +* Fri Jan 20 2023 Fedora Release Engineering - 22.3.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Mon Nov 14 2022 Karolina Surma - 22.3.1-1 +- Update to 22.3.1 +Resolves: rhbz#2135044 + +* Mon Sep 05 2022 Python Maint - 22.2.2-2 +- Fix crash when an empty dist-info/egg-info is present +Resolves: rhbz#2115001 +- No longer use the rpm_install prefix to determine RPM-installed packages +Related: rhbz#2026979 + +* Wed Aug 03 2022 Charalampos Stratakis - 22.2.2-1 +- Update to 22.2.2 +Resolves: rhbz#2109468 + +* Fri Jul 22 2022 Charalampos Stratakis - 22.2-1 +- Update to 22.2 +Resolves: rhbz#2109468 + +* Fri Jul 22 2022 Fedora Release Engineering - 22.0.4-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Wed Jun 15 2022 Python Maint - 22.0.4-4 +- Rebuilt for Python 3.11 + +* Mon Jun 13 2022 Python Maint - 22.0.4-3 +- Bootstrap for Python 3.11 + +* Tue Apr 26 2022 Tomáš Hrnčiar - 22.0.4-2 +- Fallback to pep517 if setup.py is present and setuptools cannot be imported +- Fixes: rhbz#2020635 + +* Mon Mar 21 2022 Karolina Surma - 22.0.4-1 +- Update to 22.0.4 +Resolves: rhbz#2061262 + +* Wed Feb 16 2022 Lumír Balhar - 22.0.3-1 +- Update to 22.0.3 +Resolves: rhbz#2048243 + +* Fri Jan 21 2022 Fedora Release Engineering - 21.3.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Mon Oct 25 2021 Miro Hrončok - 21.3.1-1 +- Update to 21.3.1 +- Resolves: rhbz#2016682 + +* Wed Oct 13 2021 Miro Hrončok - 21.3-1 +- Update to 21.3 +- Resolves: rhbz#2013026 +- Fix incomplete pip-updates in virtual environments + +* Wed Oct 06 2021 Charalampos Stratakis - 21.2.3-4 +- Remove bundled windows executables +- Resolves: rhbz#2005453 + +* Thu Sep 23 2021 Miro Hrončok - 21.2.3-3 +- Detect paths not to uninstall from via sysconfig's rpm_prefix install scheme + +* Mon Aug 16 2021 Miro Hrončok - 21.2.3-2 +- Fix broken uninstallation by a bogus downstream patch + +* Mon Aug 09 2021 Miro Hrončok - 21.2.3-1 +- Update to 21.2.3 +- Resolves: rhbz#1985635 + +* Fri Jul 23 2021 Fedora Release Engineering - 21.1.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Tue Jun 29 2021 Lumír Balhar - 21.1.3-1 +- Update to 21.1.3 +Resolves: rhbz#1976449 + +* Mon Jun 07 2021 Karolina Surma - 21.1.2-1 +- Update to 21.1.2 +Resolves: rhbz#1963433 + +* Fri Jun 04 2021 Python Maint - 21.1.1-3 +- Rebuilt for Python 3.10 + +* Tue Jun 01 2021 Python Maint - 21.1.1-2 +- Bootstrap for Python 3.10 + +* Mon May 10 2021 Karolina Surma - 21.1.1-1 +- Update to 21.1.1 + +* Sat Mar 13 2021 Miro Hrončok - 21.0.1-2 +- python-pip-wheel: Remove bundled provides and libcrypt recommends for Python 2 + (The wheel is Python 3 only for a while) + +* Wed Feb 17 2021 Lumír Balhar - 21.0.1-1 +- Update to 21.0.1 +Resolves: rhbz#1922592 + +* Tue Jan 26 2021 Lumír Balhar - 21.0-1 +- Update to 21.0 (#1919530) + +* Thu Dec 17 2020 Petr Viktorin - 20.3.3-1 +- Update to 20.3.3 + +* Mon Nov 30 2020 Miro Hrončok - 20.3-1 +- Update to 20.3 +- Add support for PEP 600: Future manylinux Platform Tags +- New resolver +- Fixes: rhbz#1893470 + +* Mon Oct 19 2020 Lumír Balhar - 20.2.4-1 +- Update to 20.2.4 (#1889112) + +* Wed Aug 05 2020 Tomas Orsava - 20.2.2-1 +- Update to 20.2.2 (#1838553) + +* Wed Jul 29 2020 Fedora Release Engineering - 20.1.1-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Fri Jul 10 2020 Lumír Balhar - 20.1.1-6 +- Do not emit a warning about root privileges when --root is used + +* Wed Jul 08 2020 Miro Hrončok - 20.1.1-5 +- Update bundled provides to match 20.1.1 + +* Tue Jun 16 2020 Lumír Balhar - 20.1.1-4 +- Deselect tests incompatible with the latest virtualenv + +* Sun May 24 2020 Miro Hrončok - 20.1.1-3 +- Rebuilt for Python 3.9 + +* Thu May 21 2020 Miro Hrončok - 20.1.1-2 +- Bootstrap for Python 3.9 + +* Wed May 20 2020 Tomas Hrnciar - 20.1.1-1 +- Update to 20.1.1 + +* Wed Apr 29 2020 Tomas Hrnciar - 20.1-1 +- Update to 20.1 + +* Mon Apr 27 2020 Tomas Hrnciar - 20.1~b1-1 +- Update to 20.1~b1 + +* Wed Apr 15 2020 Miro Hrončok - 20.0.2-4 +- Only recommend setuptools, don't require them + +* Fri Apr 10 2020 Miro Hrončok - 20.0.2-3 +- Allow setting $TMPDIR to $PWD/... during pip wheel (#1806625) + +* Tue Mar 10 2020 Miro Hrončok - 20.0.2-2 +- Don't warn the user about pip._internal.main() entrypoint to fix ensurepip + +* Mon Mar 02 2020 Miro Hrončok - 20.0.2-1 +- Update to 20.0.2 (#1793456) + +* Thu Jan 30 2020 Fedora Release Engineering - 19.3.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Mon Nov 04 2019 Tomas Orsava - 19.3.1-1 +- Update to 19.3.1 (#1761508) +- Drop upstreamed patch that fixed expected output in test to not break with alpha/beta/rc Python versions + +* Wed Oct 30 2019 Miro Hrončok - 19.2.3-2 +- Make /usr/bin/pip(3) work with user-installed pip 19.3+ (#1767212) + +* Mon Sep 02 2019 Miro Hrončok - 19.2.3-1 +- Update to 19.2.3 (#1742230) +- Drop patch that should strip path prefixes from RECORD files, the paths are relative + +* Wed Aug 21 2019 Petr Viktorin - 19.1.1-8 +- Remove python2-pip +- Make pip bootstrap itself, rather than with an extra bootstrap RPM build + +* Sat Aug 17 2019 Miro Hrončok - 19.1.1-7 +- Rebuilt for Python 3.8 + +* Wed Aug 14 2019 Miro Hrončok - 19.1.1-6 +- Bootstrap for Python 3.8 + +* Wed Aug 14 2019 Miro Hrončok - 19.1.1-5 +- Bootstrap for Python 3.8 + +* Fri Jul 26 2019 Fedora Release Engineering - 19.1.1-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Mon Jul 15 2019 Petr Viktorin - 19.1.1-3 +- Recommend libcrypt.so.1 for manylinux1 compatibility +- Make /usr/bin/pip Python 3 + +* Mon Jun 10 2019 Miro Hrončok - 19.1.1-2 +- Fix root warning when pip is invoked via python -m pip +- Remove a redundant second WARNING prefix form the abovementioned warning + +* Wed May 15 2019 Miro Hrončok - 19.1.1-1 +- Update to 19.1.1 (#1706995) + +* Thu Apr 25 2019 Miro Hrončok - 19.1-1 +- Update to 19.1 (#1702525) + +* Wed Mar 06 2019 Miro Hrončok - 19.0.3-1 +- Update to 19.0.3 (#1679277) + +* Wed Feb 13 2019 Miro Hrončok - 19.0.2-1 +- Update to 19.0.2 (#1668492) + +* Sat Feb 02 2019 Fedora Release Engineering - 18.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Mon Dec 03 2018 Miro Hrončok - 18.1-2 +- Use the system level root certificate instead of the one bundled in certifi + +* Thu Nov 22 2018 Miro Hrončok - 18.1-1 +- Update to 18.1 (#1652089) + +* Tue Sep 18 2018 Victor Stinner - 18.0-4 +- Prevent removing of the system packages installed under /usr/lib + when pip install -U is executed. Original patch by Michal Cyprian. + Resolves: rhbz#1550368. + +* Wed Aug 08 2018 Miro Hrončok - 18.0-3 +- Create python-pip-wheel package with the wheel + +* Tue Jul 31 2018 Miro Hrončok - 18.0-2 +- Remove redundant "Unicode" from License + +* Mon Jul 23 2018 Marcel Plch - 18.0-7 +- Update to 18.0 + +* Sat Jul 14 2018 Fedora Release Engineering - 9.0.3-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Mon Jun 18 2018 Miro Hrončok - 9.0.3-5 +- Rebuilt for Python 3.7 + +* Wed Jun 13 2018 Miro Hrončok - 9.0.3-4 +- Bootstrap for Python 3.7 + +* Wed Jun 13 2018 Miro Hrončok - 9.0.3-3 +- Bootstrap for Python 3.7 + +* Fri May 04 2018 Miro Hrončok - 9.0.3-2 +- Allow to import pip10's main from pip9's /usr/bin/pip +- Do not show the "new version of pip" warning outside of venv +Resolves: rhbz#1569488 +Resolves: rhbz#1571650 +Resolves: rhbz#1573755 + +* Thu Mar 29 2018 Charalampos Stratakis - 9.0.3-1 +- Update to 9.0.3 + +* Wed Feb 21 2018 Lumír Balhar - 9.0.1-16 +- Include built HTML documentation (in the new -doc subpackage) and man page + +* Fri Feb 09 2018 Fedora Release Engineering - 9.0.1-15 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Mon Dec 04 2017 Charalampos Stratakis - 9.0.1-14 +- Reintroduce the ipaddress module in the python3 subpackage. + +* Mon Nov 20 2017 Charalampos Stratakis - 9.0.1-13 +- Add virtual provides for the bundled libraries. (rhbz#1096912) + +* Tue Aug 29 2017 Tomas Orsava - 9.0.1-12 +- Switch macros to bcond's and make Python 2 optional to facilitate building + the Python 2 and Python 3 modules + +* Thu Jul 27 2017 Fedora Release Engineering - 9.0.1-11 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Tue May 23 2017 Tomas Orsava - 9.0.1-10 +- Modernized package descriptions +Resolves: rhbz#1452568 + +* Tue Mar 21 2017 Tomas Orsava - 9.0.1-9 +- Fix typo in the sudo pip warning + +* Fri Mar 03 2017 Tomas Orsava - 9.0.1-8 +- Patch 1 update: No sudo pip warning in venv or virtualenv + +* Thu Feb 23 2017 Tomas Orsava - 9.0.1-7 +- Patch 1 update: Customize the warning with the proper version of the pip + command + +* Tue Feb 14 2017 Tomas Orsava - 9.0.1-6 +- Added patch 1: Emit a warning when running with root privileges + +* Sat Feb 11 2017 Fedora Release Engineering - 9.0.1-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Mon Jan 02 2017 Tomas Orsava - 9.0.1-4 +- Provide symlinks to executables to comply with Fedora guidelines for Python +Resolves: rhbz#1406922 + +* Fri Dec 09 2016 Charalampos Stratakis - 9.0.1-3 +- Rebuild for Python 3.6 with wheel + +* Fri Dec 09 2016 Charalampos Stratakis - 9.0.1-2 +- Rebuild for Python 3.6 without wheel + +* Fri Nov 18 2016 Orion Poplawski - 9.0.1-1 +- Update to 9.0.1 + +* Fri Nov 18 2016 Orion Poplawski - 8.1.2-5 +- Enable EPEL Python 3 builds +- Use new python macros +- Cleanup spec + +* Fri Aug 05 2016 Tomas Orsava - 8.1.2-4 +- Updated the test sources + +* Fri Aug 05 2016 Tomas Orsava - 8.1.2-3 +- Moved python-pip into the python2-pip subpackage +- Added the python_provide macro + +* Tue Jul 19 2016 Fedora Release Engineering - 8.1.2-2 +- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages + +* Tue May 17 2016 Tomas Orsava - 8.1.2-1 +- Update to 8.1.2 +- Moved to a new PyPI URL format +- Updated the prefix-stripping patch because of upstream changes in pip/wheel.py + +* Mon Feb 22 2016 Slavek Kabrda - 8.0.2-1 +- Update to 8.0.2 + +* Thu Feb 04 2016 Fedora Release Engineering - 7.1.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Wed Oct 14 2015 Robert Kuska - 7.1.0-3 +- Rebuilt for Python3.5 rebuild +- With wheel set to 1 + +* Tue Oct 13 2015 Robert Kuska - 7.1.0-2 +- Rebuilt for Python3.5 rebuild + +* Wed Jul 01 2015 Slavek Kabrda - 7.1.0-1 +- Update to 7.1.0 + +* Tue Jun 30 2015 Ville Skyttä - 7.0.3-3 +- Install bash completion +- Ship LICENSE.txt as %%license where available + +* Thu Jun 18 2015 Fedora Release Engineering - 7.0.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Thu Jun 04 2015 Matej Stuchlik - 7.0.3-1 +- Update to 7.0.3 + +* Fri Mar 06 2015 Matej Stuchlik - 6.0.8-1 +- Update to 6.0.8 + +* Thu Dec 18 2014 Slavek Kabrda - 1.5.6-5 +- Only enable tests on Fedora. + +* Mon Dec 01 2014 Matej Stuchlik - 1.5.6-4 +- Add tests +- Add patch skipping tests requiring Internet access + +* Tue Nov 18 2014 Matej Stuchlik - 1.5.6-3 +- Added patch for local dos with predictable temp dictionary names + (http://seclists.org/oss-sec/2014/q4/655) + +* Sat Jun 07 2014 Fedora Release Engineering - 1.5.6-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Sun May 25 2014 Matej Stuchlik - 1.5.6-1 +- Update to 1.5.6 + +* Fri Apr 25 2014 Matej Stuchlik - 1.5.4-4 +- Rebuild as wheel for Python 3.4 + +* Thu Apr 24 2014 Matej Stuchlik - 1.5.4-3 +- Disable build_wheel + +* Thu Apr 24 2014 Matej Stuchlik - 1.5.4-2 +- Rebuild as wheel for Python 3.4 + +* Mon Apr 07 2014 Matej Stuchlik - 1.5.4-1 +- Updated to 1.5.4 + +* Mon Oct 14 2013 Tim Flink - 1.4.1-1 +- Removed patch for CVE 2013-2099 as it has been included in the upstream 1.4.1 release +- Updated version to 1.4.1 + +* Sun Aug 04 2013 Fedora Release Engineering - 1.3.1-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Tue Jul 16 2013 Toshio Kuratomi - 1.3.1-4 +- Fix for CVE 2013-2099 + +* Thu May 23 2013 Tim Flink - 1.3.1-3 +- undo python2 executable rename to python-pip. fixes #958377 +- fix summary to match upstream + +* Mon May 06 2013 Kevin Kofler - 1.3.1-2 +- Fix main package Summary, it's for Python 2, not 3 (#877401) + +* Fri Apr 26 2013 Jon Ciesla - 1.3.1-1 +- Update to 1.3.1, fix for CVE-2013-1888. + +* Thu Feb 14 2013 Fedora Release Engineering - 1.2.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild + +* Tue Oct 09 2012 Tim Flink - 1.2.1-2 +- Fixing files for python3-pip + +* Thu Oct 04 2012 Tim Flink - 1.2.1-1 +- Update to upstream 1.2.1 +- Change binary from pip-python to python-pip (RHBZ#855495) +- Add alias from python-pip to pip-python, to be removed at a later date + +* Tue May 15 2012 Tim Flink - 1.1.0-1 +- Update to upstream 1.1.0 + +* Sat Jan 14 2012 Fedora Release Engineering - 1.0.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Sat Oct 22 2011 Tim Flink - 1.0.2-1 +- update to 1.0.2 and added python3 subpackage + +* Wed Jun 22 2011 Tim Flink - 0.8.3-1 +- update to 0.8.3 and project home page + +* Tue Feb 08 2011 Fedora Release Engineering - 0.8.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild + +* Mon Dec 20 2010 Luke Macken - 0.8.2-1 +- update to 0.8.2 of pip +* Mon Aug 30 2010 Peter Halliday - 0.8-1 +- update to 0.8 of pip +* Thu Jul 22 2010 David Malcolm - 0.7.2-5 +- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild + +* Wed Jul 7 2010 Peter Halliday - 0.7.2-1 +- update to 0.7.2 of pip +* Sun May 23 2010 Peter Halliday - 0.7.1-1 +- update to 0.7.1 of pip +* Fri Jan 1 2010 Peter Halliday - 0.6.1.4 +- fix dependency issue +* Fri Dec 18 2009 Peter Halliday - 0.6.1-2 +- fix spec file +* Thu Dec 17 2009 Peter Halliday - 0.6.1-1 +- upgrade to 0.6.1 of pip +* Mon Aug 31 2009 Peter Halliday - 0.4-1 +- Initial package diff --git a/downstream-remove-pytest-subket.patch b/downstream-remove-pytest-subket.patch new file mode 100644 index 0000000..564794c --- /dev/null +++ b/downstream-remove-pytest-subket.patch @@ -0,0 +1,54 @@ +From 35378ae02912d704d466e4809070e17a8d13ad0a Mon Sep 17 00:00:00 2001 +From: Karolina Surma +Date: Thu, 7 Aug 2025 15:05:34 +0200 +Subject: [PATCH] Downstream-Only: Remove pytest-subket from tests + +--- + tests/conftest.py | 17 ----------------- + 1 file changed, 17 deletions(-) + +diff --git a/tests/conftest.py b/tests/conftest.py +index c98b871..ada7a7e 100644 +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -451,18 +451,6 @@ def coverage_install( + return _common_wheel_editable_install(tmpdir_factory, common_wheels, "coverage") + + +-@pytest.fixture(scope="session") +-def socket_install(tmpdir_factory: pytest.TempPathFactory, common_wheels: Path) -> Path: +- lib_dir = _common_wheel_editable_install( +- tmpdir_factory, common_wheels, "pytest_subket" +- ) +- # pytest-subket is only included so it can intercept and block unexpected +- # network requests. It should NOT be visible to the pip under test. +- dist_info = next(lib_dir.glob("*.dist-info")) +- shutil.rmtree(dist_info) +- return lib_dir +- +- + def install_pth_link( + venv: VirtualEnvironment, project_name: str, lib_dir: Path + ) -> None: +@@ -480,7 +468,6 @@ def virtualenv_template( + setuptools_install: Path, + wheel_install: Path, + coverage_install: Path, +- socket_install: Path, + ) -> VirtualEnvironment: + venv_type: VirtualEnvironmentType + if request.config.getoption("--use-venv"): +@@ -495,10 +482,6 @@ def virtualenv_template( + # Install setuptools, wheel, pytest-subket, and pip. + install_pth_link(venv, "setuptools", setuptools_install) + install_pth_link(venv, "wheel", wheel_install) +- install_pth_link(venv, "pytest_subket", socket_install) +- # Also copy pytest-subket's .pth file so it can intercept socket calls. +- with open(venv.site / "pytest_socket.pth", "w") as f: +- f.write(socket_install.joinpath("pytest_socket.pth").read_text()) + + pth, dist_info = pip_editable_parts + +-- +2.50.1 + diff --git a/dummy-certifi.patch b/dummy-certifi.patch index a9e5840..ce34415 100644 --- a/dummy-certifi.patch +++ b/dummy-certifi.patch @@ -1,15 +1,103 @@ +From 7b5fbac83944e3a0dd975ff17b69358791b68721 Mon Sep 17 00:00:00 2001 +From: Karolina Surma +Date: Thu, 27 Jun 2024 10:38:53 +0200 +Subject: [PATCH] Dummy certifi patch + +--- + src/pip/_vendor/certifi/core.py | 80 +++------------------------------ + 1 file changed, 6 insertions(+), 74 deletions(-) + diff --git a/src/pip/_vendor/certifi/core.py b/src/pip/_vendor/certifi/core.py -index 2d02ea4..b1e4975 100644 +index 2f2f7e0..bec6595 100644 --- a/src/pip/_vendor/certifi/core.py +++ b/src/pip/_vendor/certifi/core.py -@@ -11,9 +11,7 @@ import os +@@ -4,80 +4,12 @@ certifi.py + + This module returns the installation location of cacert.pem or its contents. + """ +-import sys +-import atexit + +-def exit_cacert_ctx() -> None: +- _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] ++# The RPM-packaged certifi always uses the system certificates ++def where() -> str: ++ return '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem' - def where(): -- f = os.path.dirname(__file__) +-if sys.version_info >= (3, 11): - -- return os.path.join(f, 'cacert.pem') -+ return '/etc/pki/tls/certs/ca-bundle.crt' - - - if __name__ == '__main__': +- from importlib.resources import as_file, files +- +- _CACERT_CTX = None +- _CACERT_PATH = None +- +- def where() -> str: +- # This is slightly terrible, but we want to delay extracting the file +- # in cases where we're inside of a zipimport situation until someone +- # actually calls where(), but we don't want to re-extract the file +- # on every call of where(), so we'll do it once then store it in a +- # global variable. +- global _CACERT_CTX +- global _CACERT_PATH +- if _CACERT_PATH is None: +- # This is slightly janky, the importlib.resources API wants you to +- # manage the cleanup of this file, so it doesn't actually return a +- # path, it returns a context manager that will give you the path +- # when you enter it and will do any cleanup when you leave it. In +- # the common case of not needing a temporary file, it will just +- # return the file system location and the __exit__() is a no-op. +- # +- # We also have to hold onto the actual context manager, because +- # it will do the cleanup whenever it gets garbage collected, so +- # we will also store that at the global level as well. +- _CACERT_CTX = as_file(files("pip._vendor.certifi").joinpath("cacert.pem")) +- _CACERT_PATH = str(_CACERT_CTX.__enter__()) +- atexit.register(exit_cacert_ctx) +- +- return _CACERT_PATH +- +- def contents() -> str: +- return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii") +- +-else: +- +- from importlib.resources import path as get_path, read_text +- +- _CACERT_CTX = None +- _CACERT_PATH = None +- +- def where() -> str: +- # This is slightly terrible, but we want to delay extracting the +- # file in cases where we're inside of a zipimport situation until +- # someone actually calls where(), but we don't want to re-extract +- # the file on every call of where(), so we'll do it once then store +- # it in a global variable. +- global _CACERT_CTX +- global _CACERT_PATH +- if _CACERT_PATH is None: +- # This is slightly janky, the importlib.resources API wants you +- # to manage the cleanup of this file, so it doesn't actually +- # return a path, it returns a context manager that will give +- # you the path when you enter it and will do any cleanup when +- # you leave it. In the common case of not needing a temporary +- # file, it will just return the file system location and the +- # __exit__() is a no-op. +- # +- # We also have to hold onto the actual context manager, because +- # it will do the cleanup whenever it gets garbage collected, so +- # we will also store that at the global level as well. +- _CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem") +- _CACERT_PATH = str(_CACERT_CTX.__enter__()) +- atexit.register(exit_cacert_ctx) +- +- return _CACERT_PATH +- +- def contents() -> str: +- return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii") ++def contents() -> str: ++ with open(where(), encoding='utf=8') as data: ++ return data.read() +-- +2.50.1 + diff --git a/emit-a-warning-when-running-with-root-privileges.patch b/emit-a-warning-when-running-with-root-privileges.patch deleted file mode 100644 index 9692abc..0000000 --- a/emit-a-warning-when-running-with-root-privileges.patch +++ /dev/null @@ -1,37 +0,0 @@ -diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py -index 1279d4a..aeb9d26 100644 ---- a/src/pip/_internal/commands/install.py -+++ b/src/pip/_internal/commands/install.py -@@ -5,6 +5,8 @@ import logging - import operator - import os - import shutil -+import sys -+from os import path - from optparse import SUPPRESS_HELP - - from pip._vendor import pkg_resources -@@ -217,6 +219,23 @@ class InstallCommand(RequirementCommand): - - def run(self, options, args): - cmdoptions.check_install_build_global(options) -+ -+ def is_venv(): -+ return (hasattr(sys, 'real_prefix') or -+ (hasattr(sys, 'base_prefix') and -+ sys.base_prefix != sys.prefix)) -+ -+ # Check whether we have root privileges and aren't in venv/virtualenv -+ if os.getuid() == 0 and not is_venv(): -+ command = path.basename(sys.argv[0]) -+ if command == "__main__.py": -+ command = path.basename(sys.executable) + " -m pip" -+ logger.warning( -+ "WARNING: Running pip install with root privileges is " -+ "generally not a good idea. Try `%s install --user` instead." -+ % command -+ ) -+ - upgrade_strategy = "to-satisfy-only" - if options.upgrade: - upgrade_strategy = options.upgrade_strategy diff --git a/html_theme_path.patch b/html_theme_path.patch deleted file mode 100644 index 4209471..0000000 --- a/html_theme_path.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff -ru pip-10.0.1/docs/conf.py pip-10.0.1_patched/docs/conf.py ---- pip-10.0.1/docs/html/conf.py 2018-04-14 13:17:31.000000000 +0200 -+++ pip-10.0.1_patched/docs/html/conf.py 2018-07-23 15:23:31.053267611 +0200 -@@ -134,6 +134,7 @@ - # The theme to use for HTML and HTML Help pages. Major themes that come with - # Sphinx are currently 'default' and 'sphinxdoc'. - html_theme = "pypa_theme" -+html_theme_path = ["pypa", "python-docs-theme"] - - # Theme options are theme-specific and customize the look and feel of a theme - # further. For a list of options available for each theme, see the diff --git a/local-dos.patch b/local-dos.patch deleted file mode 100644 index 721ca51..0000000 --- a/local-dos.patch +++ /dev/null @@ -1,395 +0,0 @@ -diff --git a/pip/cmdoptions.py b/pip/cmdoptions.py -index 8ed3d91..01b2104 100644 ---- a/pip/cmdoptions.py -+++ b/pip/cmdoptions.py -@@ -9,7 +9,7 @@ To be consistent, all options will follow this design. - """ - import copy - from optparse import OptionGroup, SUPPRESS_HELP, Option --from pip.locations import build_prefix, default_log_file -+from pip.locations import default_log_file - - - def make_option_group(group, parser): -@@ -297,10 +297,8 @@ build_dir = OptionMaker( - '-b', '--build', '--build-dir', '--build-directory', - dest='build_dir', - metavar='dir', -- default=build_prefix, -- help='Directory to unpack packages into and build in. ' -- 'The default in a virtualenv is "/build". ' -- 'The default for global installs is "/pip_build_".') -+ help='Directory to unpack packages into and build in.', -+) - - install_options = OptionMaker( - '--install-option', -diff --git a/pip/commands/install.py b/pip/commands/install.py -index cbf22a0..cb7d0db 100644 ---- a/pip/commands/install.py -+++ b/pip/commands/install.py -@@ -10,6 +10,7 @@ from pip.basecommand import Command - from pip.index import PackageFinder - from pip.exceptions import InstallationError, CommandError, PreviousBuildDirError - from pip import cmdoptions -+from pip.util import BuildDirectory - - - class InstallCommand(Command): -@@ -188,7 +189,7 @@ class InstallCommand(Command): - if ( - options.no_install or - options.no_download or -- (options.build_dir != build_prefix) or -+ options.build_dir or - options.no_clean - ): - logger.deprecated('1.7', 'DEPRECATION: --no-install, --no-download, --build, ' -@@ -197,7 +198,16 @@ class InstallCommand(Command): - if options.download_dir: - options.no_install = True - options.ignore_installed = True -- options.build_dir = os.path.abspath(options.build_dir) -+ -+ # If we have --no-install or --no-download and no --build we use the -+ # legacy static build dir -+ if (options.build_dir is None -+ and (options.no_install or options.no_download)): -+ options.build_dir = build_prefix -+ -+ if options.build_dir: -+ options.build_dir = os.path.abspath(options.build_dir) -+ - options.src_dir = os.path.abspath(options.src_dir) - install_options = options.install_options or [] - if options.use_user_site: -@@ -246,73 +256,75 @@ class InstallCommand(Command): - - finder = self._build_package_finder(options, index_urls, session) - -- requirement_set = RequirementSet( -- build_dir=options.build_dir, -- src_dir=options.src_dir, -- download_dir=options.download_dir, -- download_cache=options.download_cache, -- upgrade=options.upgrade, -- as_egg=options.as_egg, -- ignore_installed=options.ignore_installed, -- ignore_dependencies=options.ignore_dependencies, -- force_reinstall=options.force_reinstall, -- use_user_site=options.use_user_site, -- target_dir=temp_target_dir, -- session=session, -- pycompile=options.compile, -- ) -- for name in args: -- requirement_set.add_requirement( -- InstallRequirement.from_line(name, None)) -- for name in options.editables: -- requirement_set.add_requirement( -- InstallRequirement.from_editable(name, default_vcs=options.default_vcs)) -- for filename in options.requirements: -- for req in parse_requirements(filename, finder=finder, options=options, session=session): -- requirement_set.add_requirement(req) -- if not requirement_set.has_requirements: -- opts = {'name': self.name} -- if options.find_links: -- msg = ('You must give at least one requirement to %(name)s ' -- '(maybe you meant "pip %(name)s %(links)s"?)' % -- dict(opts, links=' '.join(options.find_links))) -- else: -- msg = ('You must give at least one requirement ' -- 'to %(name)s (see "pip help %(name)s")' % opts) -- logger.warn(msg) -- return -- -- try: -- if not options.no_download: -- requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle) -- else: -- requirement_set.locate_files() -- -- if not options.no_install and not self.bundle: -- requirement_set.install( -- install_options, -- global_options, -- root=options.root_path, -- strip_file_prefix=options.strip_file_prefix) -- installed = ' '.join([req.name for req in -- requirement_set.successfully_installed]) -- if installed: -- logger.notify('Successfully installed %s' % installed) -- elif not self.bundle: -- downloaded = ' '.join([req.name for req in -- requirement_set.successfully_downloaded]) -- if downloaded: -- logger.notify('Successfully downloaded %s' % downloaded) -- elif self.bundle: -- requirement_set.create_bundle(self.bundle_filename) -- logger.notify('Created bundle in %s' % self.bundle_filename) -- except PreviousBuildDirError: -- options.no_clean = True -- raise -- finally: -- # Clean up -- if (not options.no_clean) and ((not options.no_install) or options.download_dir): -- requirement_set.cleanup_files(bundle=self.bundle) -+ build_delete = (not (options.no_clean or options.build_dir)) -+ with BuildDirectory(options.build_dir, delete=build_delete) as build_dir: -+ requirement_set = RequirementSet( -+ build_dir=build_dir, -+ src_dir=options.src_dir, -+ download_dir=options.download_dir, -+ download_cache=options.download_cache, -+ upgrade=options.upgrade, -+ as_egg=options.as_egg, -+ ignore_installed=options.ignore_installed, -+ ignore_dependencies=options.ignore_dependencies, -+ force_reinstall=options.force_reinstall, -+ use_user_site=options.use_user_site, -+ target_dir=temp_target_dir, -+ session=session, -+ pycompile=options.compile, -+ ) -+ for name in args: -+ requirement_set.add_requirement( -+ InstallRequirement.from_line(name, None)) -+ for name in options.editables: -+ requirement_set.add_requirement( -+ InstallRequirement.from_editable(name, default_vcs=options.default_vcs)) -+ for filename in options.requirements: -+ for req in parse_requirements(filename, finder=finder, options=options, session=session): -+ requirement_set.add_requirement(req) -+ if not requirement_set.has_requirements: -+ opts = {'name': self.name} -+ if options.find_links: -+ msg = ('You must give at least one requirement to %(name)s ' -+ '(maybe you meant "pip %(name)s %(links)s"?)' % -+ dict(opts, links=' '.join(options.find_links))) -+ else: -+ msg = ('You must give at least one requirement ' -+ 'to %(name)s (see "pip help %(name)s")' % opts) -+ logger.warn(msg) -+ return -+ -+ try: -+ if not options.no_download: -+ requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle) -+ else: -+ requirement_set.locate_files() -+ -+ if not options.no_install and not self.bundle: -+ requirement_set.install( -+ install_options, -+ global_options, -+ root=options.root_path, -+ strip_file_prefix=options.strip_file_prefix) -+ installed = ' '.join([req.name for req in -+ requirement_set.successfully_installed]) -+ if installed: -+ logger.notify('Successfully installed %s' % installed) -+ elif not self.bundle: -+ downloaded = ' '.join([req.name for req in -+ requirement_set.successfully_downloaded]) -+ if downloaded: -+ logger.notify('Successfully downloaded %s' % downloaded) -+ elif self.bundle: -+ requirement_set.create_bundle(self.bundle_filename) -+ logger.notify('Created bundle in %s' % self.bundle_filename) -+ except PreviousBuildDirError: -+ options.no_clean = True -+ raise -+ finally: -+ # Clean up -+ if (not options.no_clean) and ((not options.no_install) or options.download_dir): -+ requirement_set.cleanup_files(bundle=self.bundle) - - if options.target_dir: - if not os.path.exists(options.target_dir): -diff --git a/pip/commands/wheel.py b/pip/commands/wheel.py -index 6527063..a96631a 100644 ---- a/pip/commands/wheel.py -+++ b/pip/commands/wheel.py -@@ -8,7 +8,7 @@ from pip.index import PackageFinder - from pip.log import logger - from pip.exceptions import CommandError, PreviousBuildDirError - from pip.req import InstallRequirement, RequirementSet, parse_requirements --from pip.util import normalize_path -+from pip.util import BuildDirectory, normalize_path - from pip.wheel import WheelBuilder - from pip import cmdoptions - -@@ -123,6 +123,9 @@ class WheelCommand(Command): - "--extra-index-url is suggested.") - index_urls += options.mirrors - -+ if options.build_dir: -+ options.build_dir = os.path.abspath(options.build_dir) -+ - session = self._build_session(options) - - finder = PackageFinder(find_links=options.find_links, -@@ -137,59 +140,60 @@ class WheelCommand(Command): - session=session, - ) - -- options.build_dir = os.path.abspath(options.build_dir) -- requirement_set = RequirementSet( -- build_dir=options.build_dir, -- src_dir=None, -- download_dir=None, -- download_cache=options.download_cache, -- ignore_dependencies=options.ignore_dependencies, -- ignore_installed=True, -- session=session, -- wheel_download_dir=options.wheel_dir -- ) -- -- # make the wheelhouse -- if not os.path.exists(options.wheel_dir): -- os.makedirs(options.wheel_dir) -- -- #parse args and/or requirements files -- for name in args: -- requirement_set.add_requirement( -- InstallRequirement.from_line(name, None)) -- -- for filename in options.requirements: -- for req in parse_requirements( -- filename, -- finder=finder, -- options=options, -- session=session): -- if req.editable: -- logger.notify("ignoring %s" % req.url) -- continue -- requirement_set.add_requirement(req) -- -- #fail if no requirements -- if not requirement_set.has_requirements: -- opts = {'name': self.name} -- msg = ('You must give at least one requirement ' -- 'to %(name)s (see "pip help %(name)s")' % opts) -- logger.error(msg) -- return -+ build_delete = (not (options.no_clean or options.build_dir)) -+ with BuildDirectory(options.build_dir, delete=build_delete) as build_dir: -+ requirement_set = RequirementSet( -+ build_dir=build_dir, -+ src_dir=None, -+ download_dir=None, -+ download_cache=options.download_cache, -+ ignore_dependencies=options.ignore_dependencies, -+ ignore_installed=True, -+ session=session, -+ wheel_download_dir=options.wheel_dir -+ ) - -- try: -- #build wheels -- wb = WheelBuilder( -- requirement_set, -- finder, -- options.wheel_dir, -- build_options = options.build_options or [], -- global_options = options.global_options or [] -- ) -- wb.build() -- except PreviousBuildDirError: -- options.no_clean = True -- raise -- finally: -- if not options.no_clean: -- requirement_set.cleanup_files() -+ # make the wheelhouse -+ if not os.path.exists(options.wheel_dir): -+ os.makedirs(options.wheel_dir) -+ -+ #parse args and/or requirements files -+ for name in args: -+ requirement_set.add_requirement( -+ InstallRequirement.from_line(name, None)) -+ -+ for filename in options.requirements: -+ for req in parse_requirements( -+ filename, -+ finder=finder, -+ options=options, -+ session=session): -+ if req.editable: -+ logger.notify("ignoring %s" % req.url) -+ continue -+ requirement_set.add_requirement(req) -+ -+ #fail if no requirements -+ if not requirement_set.has_requirements: -+ opts = {'name': self.name} -+ msg = ('You must give at least one requirement ' -+ 'to %(name)s (see "pip help %(name)s")' % opts) -+ logger.error(msg) -+ return -+ -+ try: -+ #build wheels -+ wb = WheelBuilder( -+ requirement_set, -+ finder, -+ options.wheel_dir, -+ build_options = options.build_options or [], -+ global_options = options.global_options or [] -+ ) -+ wb.build() -+ except PreviousBuildDirError: -+ options.no_clean = True -+ raise -+ finally: -+ if not options.no_clean: -+ requirement_set.cleanup_files() -diff --git a/pip/util.py b/pip/util.py -index f459bb2..f5edeeb 100644 ---- a/pip/util.py -+++ b/pip/util.py -@@ -8,6 +8,7 @@ import zipfile - import tarfile - import subprocess - import textwrap -+import tempfile - - from pip.exceptions import InstallationError, BadCommand, PipError - from pip.backwardcompat import(WindowsError, string_types, raw_input, -@@ -718,3 +719,35 @@ def is_prerelease(vers): - - parsed = version._normalized_key(normalized) - return any([any([y in set(["a", "b", "c", "rc", "dev"]) for y in x]) for x in parsed]) -+ -+ -+class BuildDirectory(object): -+ -+ def __init__(self, name=None, delete=None): -+ # If we were not given an explicit directory, and we were not given an -+ # explicit delete option, then we'll default to deleting. -+ if name is None and delete is None: -+ delete = True -+ -+ if name is None: -+ name = tempfile.mkdtemp(prefix="pip-build-") -+ # If we were not given an explicit directory, and we were not given -+ # an explicit delete option, then we'll default to deleting. -+ if delete is None: -+ delete = True -+ -+ self.name = name -+ self.delete = delete -+ -+ def __repr__(self): -+ return "<{} {!r}>".format(self.__class__.__name__, self.name) -+ -+ def __enter__(self): -+ return self.name -+ -+ def __exit__(self, exc, value, tb): -+ self.cleanup() -+ -+ def cleanup(self): -+ if self.delete: -+ rmtree(self.name) diff --git a/pip-allow-different-versions.patch b/pip-allow-different-versions.patch deleted file mode 100644 index 5b7075d..0000000 --- a/pip-allow-different-versions.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- /usr/bin/pip3 2018-03-29 15:22:13.000000000 +0200 -+++ pip3 2018-05-04 11:49:08.098821010 +0200 -@@ -4,7 +4,16 @@ - import re - import sys - --from pip._internal import main -+try: -+ from pip._internal import main -+except ImportError: -+ # user has most probably downgraded pip in their home -+ # so let them run it anyway until ~/.local/bin makes it in front of the PATH -+ from pip import main -+else: -+ # user might also upgraded pip... -+ if hasattr(main, 'main'): -+ main = main.main - - if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) diff --git a/plan.fmf b/plan.fmf new file mode 100644 index 0000000..72ad07b --- /dev/null +++ b/plan.fmf @@ -0,0 +1,111 @@ +execute: + how: tmt + +discover: + - name: tests_python + how: shell + url: https://src.fedoraproject.org/tests/python.git + tests: + - name: smoke36 + path: /smoke + test: VERSION=3.6 TOX=false ./venv.sh + - name: smoke39 + path: /smoke + test: VERSION=3.9 ./venv.sh + - name: smoke310 + path: /smoke + test: VERSION=3.10 ./venv.sh + - name: smoke311 + path: /smoke + test: VERSION=3.11 ./venv.sh + - name: smoke312 + path: /smoke + test: VERSION=3.12 ./venv.sh + - name: smoke313 + path: /smoke + test: VERSION=3.13 ./venv.sh + - name: smoke314 + path: /smoke + test: VERSION=3.14 ./venv.sh + - name: smoke315 + path: /smoke + test: VERSION=3.15 ./venv.sh + - name: smoke39_virtualenv + path: /smoke + test: VERSION=3.9 METHOD=virtualenv ./venv.sh + - name: smoke310_virtualenv + path: /smoke + test: VERSION=3.10 METHOD=virtualenv ./venv.sh + - name: smoke311_virtualenv + path: /smoke + test: VERSION=3.11 METHOD=virtualenv ./venv.sh + - name: smoke312_virtualenv + path: /smoke + test: VERSION=3.12 METHOD=virtualenv ./venv.sh + - name: smoke313_virtualenv + path: /smoke + test: VERSION=3.13 METHOD=virtualenv ./venv.sh + - name: smoke314_virtualenv + path: /smoke + test: VERSION=3.14 METHOD=virtualenv ./venv.sh + - name: smoke315_virtualenv + path: /smoke + test: VERSION=3.15 METHOD=virtualenv ./venv.sh + - name: rpms_pyproject-rpm-macros + how: shell + url: https://src.fedoraproject.org/rpms/pyproject-rpm-macros.git + tests: + - name: pyproject_pytest + path: /tests + test: ./mocktest.sh python-pytest + - name: pyproject_entrypoints + path: /tests + test: ./mocktest.sh python-entrypoints + - name: pyproject_pluggy + path: /tests + test: ./mocktest.sh python-pluggy + - name: pyproject_clikit + path: /tests + test: ./mocktest.sh python-clikit + - name: same_repo + how: shell + dist-git-source: true + dist-git-download-only: true + tests: + - name: mock_bootstrap_build + test: | + cd $TMT_SOURCE_DIR && + $TMT_TREE/../discover/rpms_pyproject-rpm-macros/tests/tests/mocktest.sh python-pip --without tests --without man + - name: pip_install_upgrade + path: /tests/pip_install_upgrade/ + test: ./runtest.sh + - name: bash_completion + path: /tests/bash_completion + test: ./pip_completion_full_test.sh +prepare: + - name: Install dependencies + how: install + package: + - gcc + - virtualenv + - python3.6-devel + - python3.9-devel + - python3.10-devel + - python3.11-devel + - python3.12-devel + - python3.13-devel + - python3.14-devel + - python3.15-devel + - python3-devel + - python3-tox + - mock + - rpmdevtools + - rpm-build + - grep + - util-linux + - shadow-utils + - expect + - dnf + - name: Update packages + how: shell + script: dnf upgrade -y diff --git a/python-pip.rpmlintrc b/python-pip.rpmlintrc index 2b9a037..d32664b 100644 --- a/python-pip.rpmlintrc +++ b/python-pip.rpmlintrc @@ -1,18 +1,9 @@ # This is just temporary, when upstream merges PRs it can be removed -# https://github.com/pypa/pip/pull/7959 -# https://github.com/ActiveState/appdirs/pull/144 # https://github.com/psf/requests/pull/5410 -# https://github.com/chardet/chardet/pull/192 -addFilter(r'(non-executable-script|wrong-script-interpreter) .+/pip/_internal/__init__.py\b') -addFilter(r'(non-executable-script|wrong-script-interpreter) .+/pip/_vendor/appdirs.py\b') addFilter(r'(non-executable-script|wrong-script-interpreter) .+/pip/_vendor/requests/certs.py\b') -addFilter(r'(non-executable-script|wrong-script-interpreter) .+/pip/_vendor/chardet/cli/chardetect.py\b') -# Already fixed upstream -addFilter(r'(non-executable-script|wrong-script-interpreter) .+/pip/_vendor/certifi/core.py\b') +# This file is actually a script but in the vendored context of pip, it is never executed +addFilter(r'non-executable-script .+/pip/_vendor/distro/distro.py\b') # We ship README with the main package but not with the wheel addFilter(r'python-pip-wheel.noarch: W: no-documentation') - -# SPELLING ERRORS -addFilter(r'W: spelling-error .* venv') diff --git a/python-pip.spec b/python-pip.spec index bf45f2a..8dc0aff 100644 --- a/python-pip.spec +++ b/python-pip.spec @@ -1,143 +1,115 @@ -%bcond_with bootstrap -%bcond_without tests - -%bcond_without python2 -%bcond_without doc +# The original RHEL N+1 content set is defined by (build)dependencies +# of the packages in Fedora ELN. Hence we disable tests here +# to prevent pulling many unwanted packages in. +%bcond tests %{defined fedora} +# Whether to build the manual pages (useful for bootstrapping Sphinx) +%bcond man 1 %global srcname pip -%global python_wheelname %{srcname}-%{version}-py2.py3-none-any.whl -%global python_wheeldir %{_datadir}/python-wheels - -%if %{with doc} -%global pypa_theme_commit_hash d2e63fbfc62af3b7050f619b2f5bb8658985b931 -%endif - -# Note that with disabled python3, bashcomp2 will be disabled as well because -# bashcompdir will point to a different path than with python3 enabled. -%global bashcompdir %(b=$(pkg-config --variable=completionsdir bash-completion 2>/dev/null); echo ${b:-%{_sysconfdir}/bash_completion.d}) -%if "%{bashcompdir}" != "%{_sysconfdir}/bash_completion.d" -%global bashcomp2 1 -%endif +%global base_version 25.3 +%global upstream_version %{base_version}%{?prerel} +%global python_wheel_name %{srcname}-%{upstream_version}-py3-none-any.whl Name: python-%{srcname} -# When updating, update the bundled libraries versions bellow! -# You can use vendor_meta.sh in the dist git repo -Version: 19.0.3 -Release: 7%{?dist} +Version: %{base_version}%{?prerel:~%{prerel}} +Release: %autorelease Summary: A tool for installing and managing Python packages # We bundle a lot of libraries with pip, which itself is under MIT license. # Here is the list of the libraries with corresponding licenses: -# appdirs: MIT -# distlib: Python -# distro: ASL 2.0 -# html5lib: MIT -# six: MIT -# colorama: BSD -# CacheControl: ASL 2.0 -# msgpack-python: ASL 2.0 -# lockfile: MIT -# progress: ISC -# ipaddress: Python -# packaging: ASL 2.0 or BSD -# pep517: MIT -# pyparsing: MIT -# pytoml: MIT -# retrying: ASL 2.0 -# requests: ASL 2.0 -# chardet: LGPLv2 -# idna: BSD -# urllib3: MIT -# certifi: MPLv2.0 -# rfc3986: ASL 2.0 +# certifi: MPL-2.0 +# CacheControl: Apache-2.0 +# dependency-groups: MIT +# distlib: Python-2.0.1 +# distro: Apache-2.0 +# idna: BSD-3-Clause +# msgpack: Apache-2.0 +# packaging: Apache-2.0 OR BSD-2-Clause +# platformdirs: MIT +# pygments: BSD-2-Clause +# pyproject-hooks: MIT +# requests: Apache-2.0 +# resolvelib: ISC +# rich: MIT # setuptools: MIT -# webencodings: BSD +# truststore: MIT +# tomli: MIT +# tomli-w: MIT +# urllib3: MIT -License: MIT and Python and ASL 2.0 and BSD and ISC and LGPLv2 and MPLv2.0 and (ASL 2.0 or BSD) +License: MIT AND Python-2.0.1 AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND MPL-2.0 AND (Apache-2.0 OR BSD-2-Clause) URL: https://pip.pypa.io/ -Source0: https://github.com/pypa/pip/archive/%{version}/%{srcname}-%{version}.tar.gz +Source0: https://github.com/pypa/pip/archive/%{upstream_version}/%{srcname}-%{upstream_version}.tar.gz + +# The following sources are wheels used only for tests. +# They are not bundled in the built package and do not contribute to the overall license. +# They are pre-built but only contain text files, rebuilding them in %%build has very little benefit. + +# setuptools.whl +# We cannot use RPM-packaged python-setuptools-wheel because upstream pins to <80. +# See https://github.com/pypa/pip/pull/13357 for rationale. +Source1: https://files.pythonhosted.org/packages/0d/6d/b4752b044bf94cb802d88a888dc7d288baaf77d7910b7dedda74b5ceea0c/setuptools-79.0.1-py3-none-any.whl + +# wheel.whl +# We cannot use RPM-packaged python-wheel-wheel because we intent to drop that package in wheel 0.46+. +# That version of wheel has runtime dependencies and is generally useless as a standalone wheel. +# See https://github.com/pypa/pip/pull/13382 as an attempt to drop the requirement from pip tests. +Source2: https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl + +# flit_core.whl +# This is not built as RPM-packaged wheel in Fedora at all. +Source3: https://files.pythonhosted.org/packages/f2/65/b6ba90634c984a4fcc02c7e3afe523fef500c4980fec67cc27536ee50acf/flit_core-3.12.0-py3-none-any.whl + +# coverage.whl +# There is no RPM-packaged python-coverage-wheel, the package is archful. +# Upstream uses this to measure coverage, which we don't. +# This is a dummy placeholder package that only contains empty coverage.process_startup(). +# That way, we don't need to patch the usage out of conftest.py. +Source4: coverage-0-py3-none-any.whl BuildArch: noarch %if %{with tests} BuildRequires: /usr/bin/git +BuildRequires: /usr/bin/hg BuildRequires: /usr/bin/bzr BuildRequires: /usr/bin/svn -BuildRequires: python-setuptools-wheel -BuildRequires: python-wheel-wheel +BuildRequires: python%{python3_pkgversion}-pytest-xdist %endif -# Themes required to build the docs. -%if %{with doc} -Source1: https://github.com/pypa/pypa-docs-theme/archive/%{pypa_theme_commit_hash}.tar.gz -Source2: https://github.com/python/python-docs-theme/archive/2018.2.tar.gz +%if %{with man} +# docs/requirements.txt contains many sphinx extensions +# however, we only build the manual pages thanks to +# https://github.com/pypa/pip/pull/13168 +# We also always use the "main" Sphinx, not python%%{python3_pkgversion}-sphinx +BuildRequires: python3-sphinx %endif -# Patch until the following issue gets implemented upstream: -# https://github.com/pypa/pip/issues/1351 -Patch0: allow-stripping-given-prefix-from-wheel-RECORD-files.patch - -# Downstream only patch -# Emit a warning to the user if pip install is run with root privileges -# Issue upstream: https://github.com/pypa/pip/issues/4288 -Patch1: emit-a-warning-when-running-with-root-privileges.patch - -# Add path to the doc themes to conf.py -Patch2: html_theme_path.patch - # Prevent removing of the system packages installed under /usr/lib # when pip install -U is executed. # https://bugzilla.redhat.com/show_bug.cgi?id=1550368#c24 -Patch3: remove-existing-dist-only-if-path-conflicts.patch +# Could be replaced with https://www.python.org/dev/peps/pep-0668/ +Patch: remove-existing-dist-only-if-path-conflicts.patch # Use the system level root certificate instead of the one bundled in certifi # https://bugzilla.redhat.com/show_bug.cgi?id=1655253 -Patch4: dummy-certifi.patch +# The same patch is a part of the RPM-packaged python-certifi +Patch: dummy-certifi.patch -#Patch5 intentionally skipped, not present in this branch +# pytest-subket has been introduced to intercept network calls +# https://github.com/pypa/pip/commit/a4b40f62332ccb3228b12cc5ae1493c75177247a +# We don't need a layer to check that, as we're by default in an offline environment +Patch: downstream-remove-pytest-subket.patch -# Make pip compatible with Python 3.9, backported from pip 19.2+ -# https://github.com/pypa/pip/pull/6728 -Patch6: python39.patch +# Patch for the bundled urllib3 for CVE-2025-50181 +# Redirects are not disabled when retries are disabled on PoolManager instantiation +# Upstream fix: https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857 +Patch: urllib3-CVE-2025-50181.patch -# Upgrade urllib3 to 1.25.3 -# This bundles rfc3986 -# https://github.com/pypa/pip/commit/0d620c4a03a8b3765ec45785299244e1a494d750 -# CVE-2019-11324: Certification mishandle when error should be thrown -# https://bugzilla.redhat.com/show_bug.cgi?id=1774595 -# CVE-2019-11236: CRLF injection due to not encoding the '\r\n' sequence -# https://bugzilla.redhat.com/show_bug.cgi?id=1775363 -Patch7: urllib3-1.25.3.patch - -# Upgrade requests to 2.22.0 (needed for urllib3 1.25.3) -# https://github.com/pypa/pip/commit/8e8d28dd8ecc9226ea4e0f75d54151df90f4d78e -Patch8: requests-2.22.0.patch - -# Allow setting $TMPDIR to $PWD/... during pip wheel -# This is needed to have proper debugsource packages with pyproject-rpm-macros -# https://bugzilla.redhat.com/show_bug.cgi?id=1806625 -# Backported from https://github.com/pypa/pip/pull/7873 -Patch9: 7873.patch - -# Downstream only patch -# Users might have local installations of pip from using -# `pip install --user --upgrade pip` on older/newer versions. -# If they do that and they run `pip` or `pip3`, the one from /usr/bin is used. -# However that's the one from this RPM package and the import in there might -# fail (it tries to import from ~/.local, but older or newer pip is there with -# a bit different API). -# We add this patch as a dirty workaround to make /usr/bin/pip* work with -# both pip10+ (from this RPM) and older or newer (19.3+) pip (from whatever). -# A proper fix is to put ~/.local/bin in front of /usr/bin in the PATH, -# however others are against that and we cannot change it for existing -# installs/user homes anyway. -# https://bugzilla.redhat.com/show_bug.cgi?id=1569488 -# https://bugzilla.redhat.com/show_bug.cgi?id=1571650 -# https://bugzilla.redhat.com/show_bug.cgi?id=1767212 -# WARNING: /usr/bin/pip* are entrypoints, this cannot be applied in %%prep! -# %%patch10 doesn't work outside of %%prep, so we add it as a source -Source10: pip-allow-different-versions.patch +# Remove -s from Python shebang - ensure that packages installed with pip +# to user locations are seen by pip itself +%undefine _py3_shebang_s %description pip is a package management system used to install and manage software packages @@ -148,32 +120,28 @@ Packages" or "Pip Installs Python". # Virtual provides for the packages bundled by pip. -# You can find the versions in pip/_vendor/vendor.txt file. +# You can generate it with: +# %%{_rpmconfigdir}/pythonbundles.py --namespace 'python%%{1}dist' src/pip/_vendor/vendor.txt %global bundled() %{expand: -Provides: bundled(python%{1}dist(appdirs)) = 1.4.3 -Provides: bundled(python%{1}dist(CacheControl)) = 0.12.5 -Provides: bundled(python%{1}dist(certifi)) = 2018.11.29 -Provides: bundled(python%{1}dist(chardet)) = 3.0.4 -Provides: bundled(python%{1}dist(colorama)) = 0.4.1 -Provides: bundled(python%{1}dist(distlib)) = 0.2.8 -Provides: bundled(python%{1}dist(distro)) = 1.3.0 -Provides: bundled(python%{1}dist(html5lib)) = 1.0.1 -Provides: bundled(python%{1}dist(idna)) = 2.8 -Provides: bundled(python%{1}dist(ipaddress)) = 1.0.22 -Provides: bundled(python%{1}dist(lockfile)) = 0.12.2 -Provides: bundled(python%{1}dist(msgpack)) = 0.5.6 -Provides: bundled(python%{1}dist(packaging)) = 19.0 -Provides: bundled(python%{1}dist(pep517)) = 0.5.0 -Provides: bundled(python%{1}dist(progress)) = 1.4 -Provides: bundled(python%{1}dist(pyparsing)) = 2.3.1 -Provides: bundled(python%{1}dist(pytoml)) = 0.1.20 -Provides: bundled(python%{1}dist(requests)) = 2.22.0 -Provides: bundled(python%{1}dist(retrying)) = 1.3.3 -Provides: bundled(python%{1}dist(setuptools)) = 40.6.3 -Provides: bundled(python%{1}dist(six)) = 1.12.0 -Provides: bundled(python%{1}dist(urllib3)) = 1.25.3 -Provides: bundled(python%{1}dist(rfc3986)) = 1.3.2 -Provides: bundled(python%{1}dist(webencodings)) = 0.5.1 +Provides: bundled(python%{1}dist(cachecontrol)) = 0.14.3 +Provides: bundled(python%{1}dist(certifi)) = 2025.10.5 +Provides: bundled(python%{1}dist(dependency-groups)) = 1.3.1 +Provides: bundled(python%{1}dist(distlib)) = 0.4 +Provides: bundled(python%{1}dist(distro)) = 1.9 +Provides: bundled(python%{1}dist(idna)) = 3.10 +Provides: bundled(python%{1}dist(msgpack)) = 1.1.2 +Provides: bundled(python%{1}dist(packaging)) = 25 +Provides: bundled(python%{1}dist(platformdirs)) = 4.5 +Provides: bundled(python%{1}dist(pygments)) = 2.19.2 +Provides: bundled(python%{1}dist(pyproject-hooks)) = 1.2 +Provides: bundled(python%{1}dist(requests)) = 2.32.5 +Provides: bundled(python%{1}dist(resolvelib)) = 1.2.1 +Provides: bundled(python%{1}dist(rich)) = 14.2 +Provides: bundled(python%{1}dist(setuptools)) = 70.3 +Provides: bundled(python%{1}dist(tomli)) = 2.3 +Provides: bundled(python%{1}dist(tomli-w)) = 1.2 +Provides: bundled(python%{1}dist(truststore)) = 0.10.4 +Provides: bundled(python%{1}dist(urllib3)) = 1.26.20 } # Some manylinux1 wheels need libcrypt.so.1. @@ -187,79 +155,35 @@ Provides: bundled(python%{1}dist(webencodings)) = 0.5.1 # a long time until manylinux1 is phased out). # See: https://github.com/pypa/manylinux/issues/305 # Note that manylinux is only applicable to x86 (both 32 and 64 bits) +# As of Python 3.12, we no longer use this, +# see https://discuss.python.org/t/29455/ +# However, we keep it around for previous Python versions that use the wheel package. %global crypt_compat_recommends() %{expand: Recommends: (libcrypt.so.1()(64bit) if python%{1}(x86-64)) Recommends: (libcrypt.so.1 if python%{1}(x86-32)) } -%if %{with python2} -%package -n python2-%{srcname} -Summary: A tool for installing and managing Python 2 packages -BuildRequires: python2-devel -BuildRequires: python2-setuptools -%if %{with tests} -BuildRequires: python2-mock -BuildRequires: python2-pytest -BuildRequires: python2-pretend -BuildRequires: python2-freezegun -BuildRequires: python2-scripttest -BuildRequires: python2-virtualenv -BuildRequires: python2-pyyaml -%endif -%if %{without bootstrap} -BuildRequires: python2-pip -BuildRequires: python2-wheel -%endif -BuildRequires: ca-certificates -Requires: ca-certificates -Requires: python2-setuptools - -# Virtual provides for the packages bundled by pip: -%{bundled 2} - -%{?python_provide:%python_provide python2-%{srcname}} - -%{crypt_compat_recommends 2} - -%description -n python2-%{srcname} -pip is a package management system used to install and manage software packages -written in Python. Many packages can be found in the Python Package Index -(PyPI). pip is a recursive acronym that can stand for either "Pip Installs -Packages" or "Pip Installs Python". - -%endif # with python2 - %package -n python%{python3_pkgversion}-%{srcname} Summary: A tool for installing and managing Python3 packages BuildRequires: python%{python3_pkgversion}-devel -BuildRequires: python%{python3_pkgversion}-setuptools +# python3 bootstrap: this is rebuilt before the final build of python3, which +# adds the dependency on python3-rpm-generators, so we require it manually +# Note that the package prefix is always python3-, even if we build for 3.X +# The minimal version is for bundled provides verification script +BuildRequires: python3-rpm-generators >= 11-8 +BuildRequires: pyproject-rpm-macros +BuildRequires: python%{python3_pkgversion}-flit-core BuildRequires: bash-completion -%if %{with tests} -BuildRequires: python%{python3_pkgversion}-mock -BuildRequires: python%{python3_pkgversion}-pytest -BuildRequires: python%{python3_pkgversion}-pretend -BuildRequires: python%{python3_pkgversion}-freezegun -BuildRequires: python%{python3_pkgversion}-scripttest -BuildRequires: python%{python3_pkgversion}-virtualenv -BuildRequires: python%{python3_pkgversion}-pyyaml -%endif -%if %{without bootstrap} -BuildRequires: python%{python3_pkgversion}-pip -BuildRequires: python%{python3_pkgversion}-wheel -%endif BuildRequires: ca-certificates Requires: ca-certificates -Requires: python%{python3_pkgversion}-setuptools # Virtual provides for the packages bundled by pip: -%{bundled 3} +%{bundled %{python3_pkgversion}} -%{?python_provide:%python_provide python%{python3_pkgversion}-%{srcname}} - -%{crypt_compat_recommends 3} +Provides: pip = %{version}-%{release} %description -n python%{python3_pkgversion}-%{srcname} pip is a package management system used to install and manage software packages @@ -267,542 +191,173 @@ written in Python. Many packages can be found in the Python Package Index (PyPI). pip is a recursive acronym that can stand for either "Pip Installs Packages" or "Pip Installs Python". -%if %{with doc} -%package doc -Summary: A documentation for a tool for installing and managing Python packages -BuildRequires: python%{python3_pkgversion}-sphinx - -%description doc -A documentation for a tool for installing and managing Python packages - -%endif - -%if %{without bootstrap} -%package wheel +%package -n %{python_wheel_pkg_prefix}-%{srcname}-wheel Summary: The pip wheel Requires: ca-certificates # Virtual provides for the packages bundled by pip: -%{bundled 2} -%{bundled 3} +%{bundled %{python3_pkgversion}} -%{crypt_compat_recommends 2} -%{crypt_compat_recommends 3} - -%description wheel -A Python wheel of pip to use with venv. +# This is only relevant for Pythons that are older than 3.12 and don't use their own bundled wheels +# It is also only relevant when this wheel is shared across multiple Pythons +%if "%{python_wheel_pkg_prefix}" == "python" +%{crypt_compat_recommends 3.11} +%{crypt_compat_recommends 3.10} +%{crypt_compat_recommends 3.9} %endif +%description -n %{python_wheel_pkg_prefix}-%{srcname}-wheel +A Python wheel of pip to use with venv. + %prep -%setup -q -n %{srcname}-%{version} - -%if %{with doc} -pushd docs/html -tar -xf %{SOURCE1} -mv pypa-docs-theme-%{pypa_theme_commit_hash} pypa -tar -xf %{SOURCE2} -mv python-docs-theme-2018.2 python-docs-theme -popd -%endif - -%patch0 -p1 -%patch1 -p1 -%patch2 -p1 -%patch3 -p1 -%patch4 -p1 -%patch6 -p1 -%patch7 -p1 -%patch8 -p1 -%patch9 -p1 +%autosetup -p1 -n %{srcname}-%{upstream_version} # this goes together with patch4 rm src/pip/_vendor/certifi/*.pem +# Remove windows executable binaries +rm -v src/pip/_vendor/distlib/*.exe +sed -i '/\.exe/d' pyproject.toml + +# Remove unused test requirements +sed -Ei '/(pytest-(cov|xdist|rerunfailures|subket)|proxy\.py)/d' pyproject.toml + +# Remove unused pytest-subket options +sed -Ei '/(--disable-socket|--allow-unix-socket|--allow-hosts=localhost)/d' pyproject.toml + +%if %{with tests} # tests expect wheels in here -ln -s %{python_wheeldir} tests/data/common_wheels - - -%build -%if %{with python2} && %{with bootstrap} -%py2_build -%endif - -%if %{without bootstrap} -%py3_build_wheel -%else -%py3_build -%endif - -%if %{with doc} -export PYTHONPATH=./src/ -# from tox.ini -sphinx-build-3 -b html docs/html docs/build/html -sphinx-build-3 -b man docs/man docs/build/man -c docs/html -rm -rf docs/build/html/{.doctrees,.buildinfo} -%endif - - -%install -%if %{without bootstrap} -%py3_install_wheel %{python_wheelname} -%else -%py3_install -%endif - -# TODO: we have to remove this by hand now, but it'd be nice if we wouldn't have to -# (pip install wheel doesn't overwrite) -rm %{buildroot}%{_bindir}/pip - -%if %{with doc} -pushd docs/build/man -install -d %{buildroot}%{_mandir}/man1 -for MAN in *1; do -%if %{with python2} -install -pm0644 $MAN %{buildroot}%{_mandir}/man1/$MAN -for pip in "pip2" "pip-2" "pip%{python2_version}" "pip-%{python2_version}"; do -echo ".so $MAN" > %{buildroot}%{_mandir}/man1/${MAN/pip/$pip} -done -%endif -install -pm0644 $MAN %{buildroot}%{_mandir}/man1/${MAN/pip/pip3} -for pip in "pip-3" "pip%{python3_version}" "pip-%{python3_version}"; do -echo ".so ${MAN/pip/pip3}" > %{buildroot}%{_mandir}/man1/${MAN/pip/$pip} -done -done -popd -%endif # with doc - -%if %{with python2} -%if %{without bootstrap} -%py2_install_wheel %{python_wheelname} -%else -%py2_install -%endif -%endif # with python2 - -# before we ln -s anything, we apply Source10 patch to all pips: -# we don't do this when bootstrapping because the entrypoints look different -# this is not worth dealing with because we'll rebuild once more anyway -%if %{without bootstrap} -for PIP in %{buildroot}%{_bindir}/pip*; do - patch -p1 --no-backup-if-mismatch $PIP < %{SOURCE10} -done -%endif - -mkdir -p %{buildroot}%{bashcompdir} -%if %{with python2} -PYTHONPATH=%{buildroot}%{python2_sitelib} \ - %{buildroot}%{_bindir}/pip completion --bash \ - > %{buildroot}%{bashcompdir}/pip -%endif -PYTHONPATH=%{buildroot}%{python3_sitelib} \ - %{buildroot}%{_bindir}/pip3 completion --bash \ - > %{buildroot}%{bashcompdir}/pip3 -pips2=pip -pips3=pip3 -for pip in %{buildroot}%{_bindir}/pip*; do - pip=$(basename $pip) - case $pip in -%if %{with python2} - pip2*) - pips2="$pips2 $pip" -%if 0%{?bashcomp2} - ln -s pip %{buildroot}%{bashcompdir}/$pip -%endif - ;; -%endif - pip3?*) - pips3="$pips3 $pip" -%if 0%{?bashcomp2} - ln -s pip3 %{buildroot}%{bashcompdir}/$pip -%endif - ;; - esac -done -sed -i -e "s/^\\(complete.*\\) pip\$/\\1 $pips3/" \ - -e s/_pip_completion/_pip3_completion/ \ - %{buildroot}%{bashcompdir}/pip3 - -%if %{with python2} -sed -i -e "s/^\\(complete.*\\) pip\$/\\1 $pips2/" \ - %{buildroot}%{bashcompdir}/pip -%endif - -# Provide symlinks to executables to comply with Fedora guidelines for Python -%if %{with python2} -ln -s ./pip%{python2_version} %{buildroot}%{_bindir}/pip-%{python2_version} -ln -s ./pip-%{python2_version} %{buildroot}%{_bindir}/pip-2 -%endif - -ln -s ./pip%{python3_version} %{buildroot}%{_bindir}/pip-%{python3_version} -ln -s ./pip-%{python3_version} %{buildroot}%{_bindir}/pip-3 - - -# Make sure the INSTALLER is not pip, otherwise Patch2 won't work -# TODO Maybe we should make all our python packages have this? -%if %{without bootstrap} -%if %{with python2} -echo rpm > %{buildroot}%{python2_sitelib}/pip-%{version}.dist-info/INSTALLER -%endif - -echo rpm > %{buildroot}%{python3_sitelib}/pip-%{version}.dist-info/INSTALLER -%endif - -%if %{without bootstrap} -mkdir -p %{buildroot}%{python_wheeldir} -install -p dist/%{python_wheelname} -t %{buildroot}%{python_wheeldir} +mkdir tests/data/common_wheels +cp -a %{SOURCE1} %{SOURCE2} %{SOURCE3} %{SOURCE4} tests/data/common_wheels %endif %if %{with tests} +%generate_buildrequires +# we only use this to generate test requires +# the "pyproject" part is explicitly disabled as it generates a requirement on pip +%pyproject_buildrequires -N -g test +%endif + + +%build +export PYTHONPATH=./src/ +%pyproject_wheel + +%if %{with man} +sphinx-build -t man -b man -d docs/build/doctrees/man -c docs/html docs/man docs/build/man +%endif + + +%install +export PYTHONPATH=./src/ +%pyproject_install +%pyproject_save_files -l pip + +# We'll install pip as pip3.X +# Later we'll provide symbolic links, manpage links and bashcompletion fixes for alternative names +%if "%{python3_pkgversion}" == "3" +%global alternate_names pip-%{python3_version} pip-3 pip3 pip +%else +%global alternate_names pip-%{python3_version} +%endif + +# Provide symlinks to executables +mv %{buildroot}%{_bindir}/pip %{buildroot}%{_bindir}/pip%{python3_version} +rm %{buildroot}%{_bindir}/pip3 +for pip in %{alternate_names}; do +ln -s ./pip%{python3_version} %{buildroot}%{_bindir}/$pip +done + +%if %{with man} +pushd docs/build/man +install -d %{buildroot}%{_mandir}/man1 +for MAN in *1; do +install -pm0644 $MAN %{buildroot}%{_mandir}/man1/${MAN/pip/pip%{python3_version}} +for pip in %{alternate_names}; do +echo ".so ${MAN/pip/pip%{python3_version}}" > %{buildroot}%{_mandir}/man1/${MAN/pip/$pip} +done +done +popd +%endif + +mkdir -p %{buildroot}%{bash_completions_dir} +PYTHONPATH=%{buildroot}%{python3_sitelib} \ + %{buildroot}%{_bindir}/pip%{python3_version} completion --bash \ + > %{buildroot}%{bash_completions_dir}/pip%{python3_version} + +# Make bash completion apply to all alternate names symlinks we install +sed -i -e "s/^\\(complete.*\\) pip%{python3_version}\$/\\1 pip%{python3_version} %{alternate_names}/" \ + -e s/_pip_completion/_pip%{python3_version_nodots}_completion/ \ + %{buildroot}%{bash_completions_dir}/pip%{python3_version} + +# Install the built wheel and inject SBOM into it (if the macro is available) +mkdir -p %{buildroot}%{python_wheel_dir} +install -p %{_pyproject_wheeldir}/%{python_wheel_name} -t %{buildroot}%{python_wheel_dir} +%{?python_wheel_inject_sbom:%python_wheel_inject_sbom %{buildroot}%{python_wheel_dir}/%{python_wheel_name}} + + %check +# Verify bundled provides are up to date +%{_rpmconfigdir}/pythonbundles.py src/pip/_vendor/vendor.txt --compare-with '%{bundled 3}' + +# Verify no unwanted files are present in the package +grep "exe$" %{pyproject_files} && exit 1 || true +grep "pem$" %{pyproject_files} && exit 1 || true + +# Verify we can at least run basic commands without crashing +%{py3_test_envvars} %{buildroot}%{_bindir}/pip%{python3_version} --help +%{py3_test_envvars} %{buildroot}%{_bindir}/pip%{python3_version} list +%{py3_test_envvars} %{buildroot}%{_bindir}/pip%{python3_version} show pip + +%if %{with tests} +# Upstream tests # bash completion tests only work from installed package -# needs network https://github.com/pypa/pip/pull/6263 -# test_constraints_local_editable_install_pep518 -# test_pep517_wheels_are_not_confused_with_other_files -# test_upgrade_argparse_shadowed -# TODO investigate failures -# test_uninstall_non_local_distutils -# test_venv_modification -%global pytest_k 'not completion and not test_constraints_local_editable_install_pep518 and not test_pep517_wheels_are_not_confused_with_other_files and not test_upgrade_argparse_shadowed and not test_uninstall_non_local_distutils and not test_venv_modification' +pytest_k='not completion' +# this clashes with our PYTHONPATH +pytest_k="$pytest_k and not environments_with_no_pip" +# this seems to require internet (despite no network marker) +# added in https://github.com/pypa/pip/pull/13378 TODO drop this in the next release +pytest_k="$pytest_k and not test_prompt_for_keyring_if_needed and not test_double_install_fail and not test_install_sdist_links and not test_lock_vcs and not test_lock_archive and not test_backend_sees_config_via_sdist" +# this cannot import breezy, TODO investigate +pytest_k="$pytest_k and not (functional and bazaar)" +# failures to investigate +pytest_k="$pytest_k and not test_all_fields and not test_report_mixed_not_found and not test_basic_show" # "Editable project location" missing +pytest_k="$pytest_k and not test_basic_install_from_wheel" +pytest_k="$pytest_k and not test_check_unsupported" -mkdir _bin -export PATH="$PWD/_bin:$PATH" - -%if %{with python2} -export PYTHONPATH=%{buildroot}%{python2_sitelib} -ln -s %{buildroot}%{_bindir}/pip2 _bin/pip -%{__python2} -m pytest -m 'not network' -k %{pytest_k} +%pytest -n auto -m 'not network' -k "$(echo $pytest_k)" \ + --ignore tests/functional/test_proxy.py # no proxy.py in Fedora %endif -export PYTHONPATH=%{buildroot}%{python3_sitelib} -ln -sf %{buildroot}%{_bindir}/pip3 _bin/pip -%{__python3} -m pytest -m 'not network' -k %{pytest_k} -%endif - - -%if %{with python2} -%files -n python2-%{srcname} -%license LICENSE.txt +%files -n python%{python3_pkgversion}-%{srcname} -f %{pyproject_files} %doc README.rst -%if %{with doc} -%{_mandir}/man1/pip.* -%{_mandir}/man1/pip2.* -%{_mandir}/man1/pip-2.* +%if %{with man} +%if "%{python3_pkgversion}" == "3" +%{_mandir}/man1/pip{,3,-3}.1.* +%{_mandir}/man1/pip{,3,-3}-[^3]*.1.* %endif +%{_mandir}/man1/pip{,-}%{python3_version}.1.* +%{_mandir}/man1/pip{,-}%{python3_version}-*.1.* +%endif +%if "%{python3_pkgversion}" == "3" %{_bindir}/pip -%{_bindir}/pip2 -%{_bindir}/pip-2 -%{_bindir}/pip%{python2_version} -%{_bindir}/pip-%{python2_version} -%{python2_sitelib}/pip* -%dir %{bashcompdir} -%{bashcompdir}/pip -%if 0%{?bashcomp2} -%{bashcompdir}/pip2* -%dir %(dirname %{bashcompdir}) -%endif -%endif # with python2 - -%files -n python%{python3_pkgversion}-%{srcname} -%license LICENSE.txt -%doc README.rst -%if %{with doc} -%{_mandir}/man1/pip3.* -%{_mandir}/man1/pip-3.* -%endif %{_bindir}/pip3 %{_bindir}/pip-3 +%endif %{_bindir}/pip%{python3_version} %{_bindir}/pip-%{python3_version} -%{python3_sitelib}/pip* -%dir %{bashcompdir} -%{bashcompdir}/pip3* -%if 0%{?bashcomp2} -%dir %(dirname %{bashcompdir}) -%endif +%dir %{bash_completions_dir} +%{bash_completions_dir}/pip%{python3_version} -%if %{with doc} -%files doc -%license LICENSE.txt -%doc README.rst -%doc docs/build/html -%endif # with doc -%if %{without bootstrap} -%files wheel +%files -n %{python_wheel_pkg_prefix}-%{srcname}-wheel %license LICENSE.txt # we own the dir for simplicity -%dir %{python_wheeldir}/ -%{python_wheeldir}/%{python_wheelname} -%endif +%dir %{python_wheel_dir}/ +%{python_wheel_dir}/%{python_wheel_name} %changelog -* Fri Apr 10 2020 Miro Hrončok - 19.0.3-7 -- Allow setting $TMPDIR to $PWD/... during pip wheel (#1806625) - -* Thu Jan 02 2020 Miro Hrončok - 19.0.3-6 -- Upgrade urllib3 to 1.25.3, requests to 2.22.0 -- Fix urllib3 CVE-2019-11324 (#1774595) -- Fix urllib3 CVE-2019-11236 (#1775363) - -* Mon Nov 25 2019 Miro Hrončok - 19.0.3-5 -- Make python-pip-wheel work with Python 3.9 - -* Mon Nov 11 2019 Miro Hrončok - 19.0.3-4 -- Make /usr/bin/pip(2|3) work with user-installed pip 19.3+ (#1767212) - -* Mon Jul 15 2019 Petr Viktorin - 19.0.3-3 -- Recommend libcrypt.so.1 for manylinux1 compatibility - -* Mon Jun 10 2019 Miro Hrončok - 19.0.3-2 -- Fix root warning when pip is invoked via python -m pip - -* Wed Mar 06 2019 Miro Hrončok - 19.0.3-1 -- Update to 19.0.3 (#1679277) - -* Wed Feb 13 2019 Miro Hrončok - 19.0.2-1 -- Update to 19.0.2 (#1668492) - -* Sat Feb 02 2019 Fedora Release Engineering - 18.1-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild - -* Mon Dec 03 2018 Miro Hrončok - 18.1-2 -- Use the system level root certificate instead of the one bundled in certifi - -* Thu Nov 22 2018 Miro Hrončok - 18.1-1 -- Update to 18.1 (#1652089) - -* Tue Sep 18 2018 Victor Stinner - 18.0-4 -- Prevent removing of the system packages installed under /usr/lib - when pip install -U is executed. Original patch by Michal Cyprian. - Resolves: rhbz#1550368. - -* Wed Aug 08 2018 Miro Hrončok - 18.0-3 -- Create python-pip-wheel package with the wheel - -* Tue Jul 31 2018 Miro Hrončok - 18.0-2 -- Remove redundant "Unicode"" from License - -* Mon Jul 23 2018 Marcel Plch - 18.0-7 -- Update to 18.0 - -* Sat Jul 14 2018 Fedora Release Engineering - 9.0.3-6 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild - -* Mon Jun 18 2018 Miro Hrončok - 9.0.3-5 -- Rebuilt for Python 3.7 - -* Wed Jun 13 2018 Miro Hrončok - 9.0.3-4 -- Bootstrap for Python 3.7 - -* Wed Jun 13 2018 Miro Hrončok - 9.0.3-3 -- Bootstrap for Python 3.7 - -* Fri May 04 2018 Miro Hrončok - 9.0.3-2 -- Allow to import pip10's main from pip9's /usr/bin/pip -- Do not show the "new version of pip" warning outside of venv -Resolves: rhbz#1569488 -Resolves: rhbz#1571650 -Resolves: rhbz#1573755 - -* Thu Mar 29 2018 Charalampos Stratakis - 9.0.3-1 -- Update to 9.0.3 - -* Wed Feb 21 2018 Lumír Balhar - 9.0.1-16 -- Include built HTML documentation (in the new -doc subpackage) and man page - -* Fri Feb 09 2018 Fedora Release Engineering - 9.0.1-15 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild - -* Mon Dec 04 2017 Charalampos Stratakis - 9.0.1-14 -- Reintroduce the ipaddress module in the python3 subpackage. - -* Mon Nov 20 2017 Charalampos Stratakis - 9.0.1-13 -- Add virtual provides for the bundled libraries. (rhbz#1096912) - -* Tue Aug 29 2017 Tomas Orsava - 9.0.1-12 -- Switch macros to bcond's and make Python 2 optional to facilitate building - the Python 2 and Python 3 modules - -* Thu Jul 27 2017 Fedora Release Engineering - 9.0.1-11 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild - -* Tue May 23 2017 Tomas Orsava - 9.0.1-10 -- Modernized package descriptions -Resolves: rhbz#1452568 - -* Tue Mar 21 2017 Tomas Orsava - 9.0.1-9 -- Fix typo in the sudo pip warning - -* Fri Mar 03 2017 Tomas Orsava - 9.0.1-8 -- Patch 1 update: No sudo pip warning in venv or virtualenv - -* Thu Feb 23 2017 Tomas Orsava - 9.0.1-7 -- Patch 1 update: Customize the warning with the proper version of the pip - command - -* Tue Feb 14 2017 Tomas Orsava - 9.0.1-6 -- Added patch 1: Emit a warning when running with root privileges - -* Sat Feb 11 2017 Fedora Release Engineering - 9.0.1-5 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild - -* Mon Jan 02 2017 Tomas Orsava - 9.0.1-4 -- Provide symlinks to executables to comply with Fedora guidelines for Python -Resolves: rhbz#1406922 - -* Fri Dec 09 2016 Charalampos Stratakis - 9.0.1-3 -- Rebuild for Python 3.6 with wheel - -* Fri Dec 09 2016 Charalampos Stratakis - 9.0.1-2 -- Rebuild for Python 3.6 without wheel - -* Fri Nov 18 2016 Orion Poplawski - 9.0.1-1 -- Update to 9.0.1 - -* Fri Nov 18 2016 Orion Poplawski - 8.1.2-5 -- Enable EPEL Python 3 builds -- Use new python macros -- Cleanup spec - -* Fri Aug 05 2016 Tomas Orsava - 8.1.2-4 -- Updated the test sources - -* Fri Aug 05 2016 Tomas Orsava - 8.1.2-3 -- Moved python-pip into the python2-pip subpackage -- Added the python_provide macro - -* Tue Jul 19 2016 Fedora Release Engineering - 8.1.2-2 -- https://fedoraproject.org/wiki/Changes/Automatic_Provides_for_Python_RPM_Packages - -* Tue May 17 2016 Tomas Orsava - 8.1.2-1 -- Update to 8.1.2 -- Moved to a new PyPI URL format -- Updated the prefix-stripping patch because of upstream changes in pip/wheel.py - -* Mon Feb 22 2016 Slavek Kabrda - 8.0.2-1 -- Update to 8.0.2 - -* Thu Feb 04 2016 Fedora Release Engineering - 7.1.0-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild - -* Wed Oct 14 2015 Robert Kuska - 7.1.0-3 -- Rebuilt for Python3.5 rebuild -- With wheel set to 1 - -* Tue Oct 13 2015 Robert Kuska - 7.1.0-2 -- Rebuilt for Python3.5 rebuild - -* Wed Jul 01 2015 Slavek Kabrda - 7.1.0-1 -- Update to 7.1.0 - -* Tue Jun 30 2015 Ville Skyttä - 7.0.3-3 -- Install bash completion -- Ship LICENSE.txt as %%license where available - -* Thu Jun 18 2015 Fedora Release Engineering - 7.0.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild - -* Thu Jun 04 2015 Matej Stuchlik - 7.0.3-1 -- Update to 7.0.3 - -* Fri Mar 06 2015 Matej Stuchlik - 6.0.8-1 -- Update to 6.0.8 - -* Thu Dec 18 2014 Slavek Kabrda - 1.5.6-5 -- Only enable tests on Fedora. - -* Mon Dec 01 2014 Matej Stuchlik - 1.5.6-4 -- Add tests -- Add patch skipping tests requiring Internet access - -* Tue Nov 18 2014 Matej Stuchlik - 1.5.6-3 -- Added patch for local dos with predictable temp dictionary names - (http://seclists.org/oss-sec/2014/q4/655) - -* Sat Jun 07 2014 Fedora Release Engineering - 1.5.6-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild - -* Sun May 25 2014 Matej Stuchlik - 1.5.6-1 -- Update to 1.5.6 - -* Fri Apr 25 2014 Matej Stuchlik - 1.5.4-4 -- Rebuild as wheel for Python 3.4 - -* Thu Apr 24 2014 Matej Stuchlik - 1.5.4-3 -- Disable build_wheel - -* Thu Apr 24 2014 Matej Stuchlik - 1.5.4-2 -- Rebuild as wheel for Python 3.4 - -* Mon Apr 07 2014 Matej Stuchlik - 1.5.4-1 -- Updated to 1.5.4 - -* Mon Oct 14 2013 Tim Flink - 1.4.1-1 -- Removed patch for CVE 2013-2099 as it has been included in the upstream 1.4.1 release -- Updated version to 1.4.1 - -* Sun Aug 04 2013 Fedora Release Engineering - 1.3.1-5 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild - -* Tue Jul 16 2013 Toshio Kuratomi - 1.3.1-4 -- Fix for CVE 2013-2099 - -* Thu May 23 2013 Tim Flink - 1.3.1-3 -- undo python2 executable rename to python-pip. fixes #958377 -- fix summary to match upstream - -* Mon May 06 2013 Kevin Kofler - 1.3.1-2 -- Fix main package Summary, it's for Python 2, not 3 (#877401) - -* Fri Apr 26 2013 Jon Ciesla - 1.3.1-1 -- Update to 1.3.1, fix for CVE-2013-1888. - -* Thu Feb 14 2013 Fedora Release Engineering - 1.2.1-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild - -* Tue Oct 09 2012 Tim Flink - 1.2.1-2 -- Fixing files for python3-pip - -* Thu Oct 04 2012 Tim Flink - 1.2.1-1 -- Update to upstream 1.2.1 -- Change binary from pip-python to python-pip (RHBZ#855495) -- Add alias from python-pip to pip-python, to be removed at a later date - -* Tue May 15 2012 Tim Flink - 1.1.0-1 -- Update to upstream 1.1.0 - -* Sat Jan 14 2012 Fedora Release Engineering - 1.0.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild - -* Sat Oct 22 2011 Tim Flink - 1.0.2-1 -- update to 1.0.2 and added python3 subpackage - -* Wed Jun 22 2011 Tim Flink - 0.8.3-1 -- update to 0.8.3 and project home page - -* Tue Feb 08 2011 Fedora Release Engineering - 0.8.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild - -* Mon Dec 20 2010 Luke Macken - 0.8.2-1 -- update to 0.8.2 of pip -* Mon Aug 30 2010 Peter Halliday - 0.8-1 -- update to 0.8 of pip -* Thu Jul 22 2010 David Malcolm - 0.7.2-5 -- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild - -* Wed Jul 7 2010 Peter Halliday - 0.7.2-1 -- update to 0.7.2 of pip -* Sun May 23 2010 Peter Halliday - 0.7.1-1 -- update to 0.7.1 of pip -* Fri Jan 1 2010 Peter Halliday - 0.6.1.4 -- fix dependency issue -* Fri Dec 18 2009 Peter Halliday - 0.6.1-2 -- fix spec file -* Thu Dec 17 2009 Peter Halliday - 0.6.1-1 -- upgrade to 0.6.1 of pip -* Mon Aug 31 2009 Peter Halliday - 0.4-1 -- Initial package - +%autochangelog diff --git a/python39.patch b/python39.patch deleted file mode 100644 index 6b4219b..0000000 --- a/python39.patch +++ /dev/null @@ -1,45 +0,0 @@ -From ef7ca1472c1fdd085cffb8183b7ce8abbe9e2800 Mon Sep 17 00:00:00 2001 -From: Chih-Hsuan Yen -Date: Thu, 18 Jul 2019 12:45:15 +0800 -Subject: [PATCH] Add an html5lib patch for Python 3.9 compatibility - -The patch is adapted from https://github.com/html5lib/html5lib-python/commit/4f9235752cea29c5a31721440578b430823a1e69 - -Closes https://github.com/pypa/pip/issues/6407 -Closes https://github.com/pypa/pip/issues/6237 ---- - src/pip/_vendor/html5lib/_trie/_base.py | 5 +++- - src/pip/_vendor/html5lib/treebuilders/dom.py | 5 +++- - 4 files changed, 40 insertions(+), 2 deletions(-) - -diff --git a/src/pip/_vendor/html5lib/_trie/_base.py b/src/pip/_vendor/html5lib/_trie/_base.py -index a1158bbbf..6b71975f0 100644 ---- a/src/pip/_vendor/html5lib/_trie/_base.py -+++ b/src/pip/_vendor/html5lib/_trie/_base.py -@@ -1,6 +1,9 @@ - from __future__ import absolute_import, division, unicode_literals - --from collections import Mapping -+try: -+ from collections.abc import Mapping -+except ImportError: # Python 2.7 -+ from collections import Mapping - - - class Trie(Mapping): -diff --git a/src/pip/_vendor/html5lib/treebuilders/dom.py b/src/pip/_vendor/html5lib/treebuilders/dom.py -index dcfac220b..d8b530046 100644 ---- a/src/pip/_vendor/html5lib/treebuilders/dom.py -+++ b/src/pip/_vendor/html5lib/treebuilders/dom.py -@@ -1,7 +1,10 @@ - from __future__ import absolute_import, division, unicode_literals - - --from collections import MutableMapping -+try: -+ from collections.abc import MutableMapping -+except ImportError: # Python 2.7 -+ from collections import MutableMapping - from xml.dom import minidom, Node - import weakref - diff --git a/remove-existing-dist-only-if-path-conflicts.patch b/remove-existing-dist-only-if-path-conflicts.patch index e8fbe93..d280f34 100644 --- a/remove-existing-dist-only-if-path-conflicts.patch +++ b/remove-existing-dist-only-if-path-conflicts.patch @@ -1,88 +1,115 @@ -commit b6d5da6796801862eb751a93d507c343af0604d6 -Author: Victor Stinner -Date: Tue Sep 18 17:13:51 2018 +0200 +From d5cb806a14bb50a96484bca6536e81f2ac316b9d Mon Sep 17 00:00:00 2001 +From: Karolina Surma +Date: Wed, 16 Feb 2022 08:36:21 +0100 +Subject: [PATCH] Prevent removing of the system packages installed under + /usr/lib when pip install --upgrade is executed. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit - Subject: Prevent removing of the system packages installed under /usr/lib - - when pip install -U is executed. - - Resolves: rhbz#1550368 - - Co-Authored-By: Michal Cyprian +Resolves: rhbz#1550368 +Co-Authored-By: Michal Cyprian +Co-Authored-By: Victor Stinner +Co-Authored-By: Petr Viktorin +Co-Authored-By: Lumir Balhar +Co-Authored-By: Miro Hrončok +Co-Authored-By: Karolina Surma +--- + src/pip/_internal/metadata/base.py | 12 +++++++++++- + src/pip/_internal/req/req_install.py | 2 +- + src/pip/_internal/resolution/legacy/resolver.py | 4 +++- + src/pip/_internal/resolution/resolvelib/factory.py | 12 ++++++++++++ + 4 files changed, 27 insertions(+), 3 deletions(-) + +diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py +index 230e114..8bd5d31 100644 +--- a/src/pip/_internal/metadata/base.py ++++ b/src/pip/_internal/metadata/base.py +@@ -23,7 +23,7 @@ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name + from pip._vendor.packaging.version import Version + + from pip._internal.exceptions import NoneMetadataError +-from pip._internal.locations import site_packages, user_site ++from pip._internal.locations import get_scheme, site_packages, user_site + from pip._internal.models.direct_url import ( + DIRECT_URL_METADATA_NAME, + DirectUrl, +@@ -575,6 +575,16 @@ class BaseDistribution(Protocol): + for extra in self._iter_egg_info_extras(): + metadata["Provides-Extra"] = extra + ++ @property ++ def in_install_path(self) -> bool: ++ """ ++ Return True if given Distribution is installed in ++ path matching distutils_scheme layout. ++ """ ++ norm_path = normalize_path(self.installed_location) ++ return norm_path.startswith(normalize_path( ++ get_scheme("").purelib.split('python')[0])) ++ + + class BaseEnvironment: + """An environment containing distributions to introspect.""" diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py -index d21530a..0023a35 100644 +index c9f6bff..c101826 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py -@@ -29,7 +29,7 @@ from pip._internal.utils.hashes import Hashes - from pip._internal.utils.logging import indent_log - from pip._internal.utils.misc import ( - _make_build_dir, ask_path_exists, backup_dir, call_subprocess, -- display_path, dist_in_site_packages, dist_in_usersite, ensure_dir, -+ display_path, dist_in_install_path, dist_in_site_packages, dist_in_usersite, ensure_dir, - get_installed_version, redact_password_from_url, rmtree, - ) - from pip._internal.utils.packaging import get_metadata -@@ -411,7 +411,7 @@ class InstallRequirement(object): - "lack sys.path precedence to %s in %s" % - (existing_dist.project_name, existing_dist.location) +@@ -453,7 +453,7 @@ class InstallRequirement: + f"lack sys.path precedence to {existing_dist.raw_name} " + f"in {existing_dist.location}" ) - else: -+ elif dist_in_install_path(existing_dist): - self.conflicts_with = existing_dist - return True - -diff --git a/src/pip/_internal/resolve.py b/src/pip/_internal/resolve.py -index 33f572f..88b68e1 100644 ---- a/src/pip/_internal/resolve.py -+++ b/src/pip/_internal/resolve.py -@@ -20,7 +20,7 @@ from pip._internal.exceptions import ( - ) - from pip._internal.req.constructors import install_req_from_req_string - from pip._internal.utils.logging import indent_log --from pip._internal.utils.misc import dist_in_usersite, ensure_dir -+from pip._internal.utils.misc import dist_in_install_path, dist_in_usersite, ensure_dir - from pip._internal.utils.packaging import check_dist_requires_python - from pip._internal.utils.typing import MYPY_CHECK_RUNNING - -@@ -154,7 +154,9 @@ class Resolver(object): - """ ++ elif existing_dist.in_install_path: + self.should_reinstall = True + else: + if self.editable: +diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py +index 33a4fdc..1fe886e 100644 +--- a/src/pip/_internal/resolution/legacy/resolver.py ++++ b/src/pip/_internal/resolution/legacy/resolver.py +@@ -322,7 +322,9 @@ class Resolver(BaseResolver): # Don't uninstall the conflict if doing a user install and the # conflict is not a user install. -- if not self.use_user_site or dist_in_usersite(req.satisfied_by): + assert req.satisfied_by is not None +- if not self.use_user_site or req.satisfied_by.in_usersite: + if ((not self.use_user_site -+ or dist_in_usersite(req.satisfied_by)) -+ and dist_in_install_path(req.satisfied_by)): - req.conflicts_with = req.satisfied_by ++ or req.satisfied_by.in_usersite) ++ and req.satisfied_by.in_install_path): + req.should_reinstall = True req.satisfied_by = None -diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py -index 84605ee..0e4ba93 100644 ---- a/src/pip/_internal/utils/misc.py -+++ b/src/pip/_internal/utils/misc.py -@@ -30,7 +30,7 @@ from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote - from pip._internal.exceptions import CommandError, InstallationError - from pip._internal.locations import ( - running_under_virtualenv, site_packages, user_site, virtualenv_no_global, -- write_delete_marker_file, -+ write_delete_marker_file, distutils_scheme, - ) - from pip._internal.utils.compat import ( - WINDOWS, console_to_str, expanduser, stdlib_pkgs, -@@ -355,6 +355,16 @@ def dist_in_site_packages(dist): - ).startswith(normalize_path(site_packages)) +diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py +index f23e4cd..1bada79 100644 +--- a/src/pip/_internal/resolution/resolvelib/factory.py ++++ b/src/pip/_internal/resolution/resolvelib/factory.py +@@ -3,6 +3,8 @@ from __future__ import annotations + import contextlib + import functools + import logging ++import sys ++import sysconfig + from collections.abc import Iterable, Iterator, Mapping, Sequence + from typing import ( + TYPE_CHECKING, +@@ -615,6 +617,16 @@ class Factory: + if dist is None: # Not installed, no uninstallation required. + return None - -+def dist_in_install_path(dist): -+ """ -+ Return True if given Distribution is installed in -+ path matching distutils_scheme layout. -+ """ -+ norm_path = normalize_path(dist_location(dist)) -+ return norm_path.startswith(normalize_path( -+ distutils_scheme("")['purelib'].split('python')[0])) ++ # Prevent uninstalling packages from /usr ++ try: ++ if dist.installed_location in ( ++ sysconfig.get_path('purelib', scheme='posix_prefix', vars={'base': sys.base_prefix}), ++ sysconfig.get_path('platlib', scheme='posix_prefix', vars={'platbase': sys.base_prefix}), ++ ): ++ return None ++ except KeyError: # this Python doesn't have 'rpm_prefix' scheme yet ++ pass + -+ - def dist_is_editable(dist): - # type: (Distribution) -> bool - """ + # We're installing into global site. The current installation must + # be uninstalled, no matter it's in global or user site, because the + # user site installation has precedence over global. +-- +2.50.1 + diff --git a/requests-2.22.0.patch b/requests-2.22.0.patch deleted file mode 100644 index 08857cd..0000000 --- a/requests-2.22.0.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 8e8d28dd8ecc9226ea4e0f75d54151df90f4d78e Mon Sep 17 00:00:00 2001 -From: Pradyun Gedam -Date: Sat, 20 Jul 2019 09:31:48 +0530 -Subject: [PATCH] Upgrade requests to 2.22.0 - ---- - news/requests.vendor | 1 + - src/pip/_vendor/requests/__init__.py | 4 ++-- - src/pip/_vendor/requests/__version__.py | 6 +++--- - src/pip/_vendor/requests/api.py | 4 ++-- - src/pip/_vendor/vendor.txt | 1 + - 5 files changed, 9 insertions(+), 7 deletions(-) - create mode 100644 news/requests.vendor - -diff --git a/news/requests.vendor b/news/requests.vendor -new file mode 100644 -index 0000000000..aac729b0e1 ---- /dev/null -+++ b/news/requests.vendor -@@ -0,0 +1 @@ -+Upgrade requests to 2.22.0 -diff --git a/src/pip/_vendor/requests/__init__.py b/src/pip/_vendor/requests/__init__.py -index 80c4ce1d21..1d30e3e063 100644 ---- a/src/pip/_vendor/requests/__init__.py -+++ b/src/pip/_vendor/requests/__init__.py -@@ -57,10 +57,10 @@ def check_compatibility(urllib3_version, chardet_version): - # Check urllib3 for compatibility. - major, minor, patch = urllib3_version # noqa: F811 - major, minor, patch = int(major), int(minor), int(patch) -- # urllib3 >= 1.21.1, <= 1.24 -+ # urllib3 >= 1.21.1, <= 1.25 - assert major == 1 - assert minor >= 21 -- assert minor <= 24 -+ assert minor <= 25 - - # Check chardet for compatibility. - major, minor, patch = chardet_version.split('.')[:3] -diff --git a/src/pip/_vendor/requests/__version__.py b/src/pip/_vendor/requests/__version__.py -index f5b5d03671..9844f740ab 100644 ---- a/src/pip/_vendor/requests/__version__.py -+++ b/src/pip/_vendor/requests/__version__.py -@@ -5,10 +5,10 @@ - __title__ = 'requests' - __description__ = 'Python HTTP for Humans.' - __url__ = 'http://python-requests.org' --__version__ = '2.21.0' --__build__ = 0x022100 -+__version__ = '2.22.0' -+__build__ = 0x022200 - __author__ = 'Kenneth Reitz' - __author_email__ = 'me@kennethreitz.org' - __license__ = 'Apache 2.0' --__copyright__ = 'Copyright 2018 Kenneth Reitz' -+__copyright__ = 'Copyright 2019 Kenneth Reitz' - __cake__ = u'\u2728 \U0001f370 \u2728' -diff --git a/src/pip/_vendor/requests/api.py b/src/pip/_vendor/requests/api.py -index abada96d46..ef71d0759e 100644 ---- a/src/pip/_vendor/requests/api.py -+++ b/src/pip/_vendor/requests/api.py -@@ -19,7 +19,7 @@ def request(method, url, **kwargs): - :param method: method for the new :class:`Request` object. - :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary, list of tuples or bytes to send -- in the body of the :class:`Request`. -+ in the query string for the :class:`Request`. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. -@@ -65,7 +65,7 @@ def get(url, params=None, **kwargs): - - :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary, list of tuples or bytes to send -- in the body of the :class:`Request`. -+ in the query string for the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response ` object - :rtype: requests.Response -diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt -index bcf579515e..e5542fbc5e 100644 ---- a/src/pip/_vendor/vendor.txt -+++ b/src/pip/_vendor/vendor.txt -@@ -12,7 +12,7 @@ pep517==0.5.0 - progress==1.4 - pyparsing==2.3.1 - pytoml==0.1.20 --requests==2.21.0 -+requests==2.22.0 - certifi==2018.11.29 - chardet==3.0.4 - idna==2.8 diff --git a/skip-network-tests.patch b/skip-network-tests.patch deleted file mode 100644 index 9dec67e..0000000 --- a/skip-network-tests.patch +++ /dev/null @@ -1,1055 +0,0 @@ -diff -up pip-1.5.6/tests/functional/test_freeze.py.orig pip-1.5.6/tests/functional/test_freeze.py ---- pip-1.5.6/tests/functional/test_freeze.py.orig 2014-11-27 11:23:19.947665742 +0100 -+++ pip-1.5.6/tests/functional/test_freeze.py 2014-11-27 11:30:45.937308998 +0100 -@@ -1,6 +1,7 @@ - import sys - import re - import textwrap -+import pytest - from doctest import OutputChecker, ELLIPSIS - - from tests.lib.local_repos import local_checkout, local_repo -@@ -58,6 +59,7 @@ def test_freeze_basic(script): - _check_output(result, expected) - - -+@pytest.mark.skipif(True, reason='No network') - def test_freeze_svn(script, tmpdir): - """Test freezing a svn checkout""" - -@@ -79,6 +81,7 @@ def test_freeze_svn(script, tmpdir): - _check_output(result, expected) - - -+@pytest.mark.skipif(True, reason='No network') - def test_freeze_git_clone(script, tmpdir): - """ - Test freezing a Git clone. -@@ -109,6 +112,7 @@ def test_freeze_git_clone(script, tmpdir - _check_output(result, expected) - - -+@pytest.mark.skipif(True, reason='No network') - def test_freeze_mercurial_clone(script, tmpdir): - """ - Test freezing a Mercurial clone. -@@ -140,6 +144,7 @@ def test_freeze_mercurial_clone(script, - _check_output(result, expected) - - -+@pytest.mark.skipif(True, reason='No network') - def test_freeze_bazaar_clone(script, tmpdir): - """ - Test freezing a Bazaar clone. -@@ -175,6 +180,7 @@ def test_freeze_bazaar_clone(script, tmp - _check_output(result, expected) - - -+@pytest.mark.skipif(True, reason='No network') - def test_freeze_with_local_option(script): - """ - Test that wsgiref (from global site-packages) is reported normally, but not with --local. -@@ -205,6 +211,7 @@ def test_freeze_with_local_option(script - _check_output(result, expected) - - -+@pytest.mark.skipif(True, reason='No network') - def test_freeze_with_requirement_option(script): - """ - Test that new requirements are created correctly with --requirement hints -diff -up pip-1.5.6/tests/functional/test_install_cleanup.py.orig pip-1.5.6/tests/functional/test_install_cleanup.py ---- pip-1.5.6/tests/functional/test_install_cleanup.py.orig 2014-11-27 11:23:20.863671103 +0100 -+++ pip-1.5.6/tests/functional/test_install_cleanup.py 2014-11-27 14:25:05.086297126 +0100 -@@ -1,4 +1,5 @@ - import os -+import pytest - - from os.path import abspath, exists, join - -@@ -20,6 +21,7 @@ def test_cleanup_after_install(script, d - script.assert_no_temp() - - -+@pytest.mark.skipif(True, reason='No network') - def test_no_clean_option_blocks_cleaning_after_install(script, data): - """ - Test --no-clean option blocks cleaning after install -@@ -29,6 +31,7 @@ def test_no_clean_option_blocks_cleaning - assert exists(build), "build/simple should still exist %s" % str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_cleanup_after_install_editable_from_hg(script, tmpdir): - """ - Test clean up after cloning from Mercurial. -@@ -87,6 +90,7 @@ def test_cleanup_req_satisifed_no_name(s - script.assert_no_temp() - - -+@pytest.mark.skipif(True, reason='No network') - def test_download_should_not_delete_existing_build_dir(script): - """ - It should not delete build/ if existing before run the command -@@ -123,6 +127,7 @@ def test_cleanup_after_egg_info_exceptio - script.assert_no_temp() - - -+@pytest.mark.skipif(True, reason='No network') - def test_cleanup_prevented_upon_build_dir_exception(script, data): - """ - Test no cleanup occurs after a PreviousBuildDirError -diff -up pip-1.5.6/tests/functional/test_install_compat.py.orig pip-1.5.6/tests/functional/test_install_compat.py ---- pip-1.5.6/tests/functional/test_install_compat.py.orig 2014-11-27 11:23:20.912671389 +0100 -+++ pip-1.5.6/tests/functional/test_install_compat.py 2014-11-27 11:30:36.533251976 +0100 -@@ -3,9 +3,11 @@ Tests for compatibility workarounds. - - """ - import os -+import pytest - from tests.lib import pyversion, assert_all_changes - - -+@pytest.mark.skipif(True, reason='No network') - def test_debian_egg_name_workaround(script): - """ - We can uninstall packages installed with the pyversion removed from the -diff -up pip-1.5.6/tests/functional/test_install_config.py.orig pip-1.5.6/tests/functional/test_install_config.py ---- pip-1.5.6/tests/functional/test_install_config.py.orig 2014-11-27 11:23:20.920671436 +0100 -+++ pip-1.5.6/tests/functional/test_install_config.py 2014-11-27 11:30:03.530051860 +0100 -@@ -1,7 +1,7 @@ - import os - import tempfile - import textwrap -- -+import pytest - - def test_options_from_env_vars(script): - """ -@@ -28,6 +28,7 @@ def test_command_line_options_override_e - assert "Getting page http://download.zope.org/ppix" in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_env_vars_override_config_file(script, virtualenv): - """ - Test that environmental variables override settings in config files. -@@ -62,6 +63,7 @@ def _test_env_vars_override_config_file( - assert "Successfully installed INITools" in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_command_line_append_flags(script, virtualenv, data): - """ - Test command line flags that append to defaults set by environmental variables. -@@ -76,6 +78,7 @@ def test_command_line_append_flags(scrip - assert "Skipping link %s" % data.find_links in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_command_line_appends_correctly(script, data): - """ - Test multiple appending options set by environmental variables. -diff -up pip-1.5.6/tests/functional/test_install_download.py.orig pip-1.5.6/tests/functional/test_install_download.py ---- pip-1.5.6/tests/functional/test_install_download.py.orig 2014-11-27 11:23:20.970671729 +0100 -+++ pip-1.5.6/tests/functional/test_install_download.py 2014-11-27 11:29:42.503924368 +0100 -@@ -1,9 +1,11 @@ - import os - import textwrap -+import pytest - - from tests.lib.path import Path - - -+@pytest.mark.skipif(True, reason='No network') - def test_download_if_requested(script): - """ - It should download (in the scratch path) and not install if requested. -@@ -13,6 +15,7 @@ def test_download_if_requested(script): - assert script.site_packages/ 'initools' not in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_download_wheel(script): - """ - Test using "pip install --download" to download a *.whl archive. -@@ -26,6 +29,7 @@ def test_download_wheel(script): - assert script.site_packages/ 'piptestpackage' not in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_single_download_from_requirements_file(script): - """ - It should support download (in the scratch path) from PyPi from a requirements file -@@ -38,6 +42,7 @@ def test_single_download_from_requiremen - assert script.site_packages/ 'initools' not in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_download_should_download_dependencies(script): - """ - It should download dependencies (in the scratch path) -@@ -77,6 +82,7 @@ def test_download_should_download_wheel_ - assert Path('scratch') / dep_filename in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_download_should_skip_existing_files(script): - """ - It should not download files already existing in the scratch dir -diff -up pip-1.5.6/tests/functional/test_install_extras.py.orig pip-1.5.6/tests/functional/test_install_extras.py ---- pip-1.5.6/tests/functional/test_install_extras.py.orig 2014-11-27 11:23:21.066672291 +0100 -+++ pip-1.5.6/tests/functional/test_install_extras.py 2014-11-27 11:31:01.854405511 +0100 -@@ -1,6 +1,8 @@ -+import pytest - from os.path import join - - -+@pytest.mark.skipif(True, reason='No network') - def test_simple_extras_install_from_pypi(script): - """ - Test installing a package from PyPI using extras dependency Paste[openid]. -@@ -10,6 +12,7 @@ def test_simple_extras_install_from_pypi - assert initools_folder in result.files_created, result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_no_extras_uninstall(script): - """ - No extras dependency gets uninstalled when the root package is uninstalled -diff -up pip-1.5.6/tests/functional/test_install.py.orig pip-1.5.6/tests/functional/test_install.py ---- pip-1.5.6/tests/functional/test_install.py.orig 2014-11-27 11:23:20.227667380 +0100 -+++ pip-1.5.6/tests/functional/test_install.py 2014-11-27 11:23:20.825670880 +0100 -@@ -13,6 +13,7 @@ from tests.lib.local_repos import local_ - from tests.lib.path import Path - - -+@pytest.mark.skipif(True, reason='No network') - def test_without_setuptools(script): - script.run("pip", "uninstall", "setuptools", "-y") - result = script.run( -@@ -26,6 +27,7 @@ def test_without_setuptools(script): - ) - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_second_command_line_interface_works(script): - """ - Check if ``pip`` commands behaves equally -@@ -39,6 +41,7 @@ def test_pip_second_command_line_interfa - assert initools_folder in result.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_from_pypi(script): - """ - Test installing a package from PyPI. -@@ -60,6 +63,7 @@ def test_editable_install(script): - assert not result.files_updated, result.files_updated - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_editable_from_svn(script, tmpdir): - """ - Test checking out from svn. -@@ -71,6 +75,7 @@ def test_install_editable_from_svn(scrip - result.assert_installed('INITools', with_files=['.svn']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_download_editable_to_custom_path(script, tmpdir): - """ - Test downloading an editable using a relative custom src folder. -@@ -94,6 +99,7 @@ def test_download_editable_to_custom_pat - assert customdl_files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_editable_no_install_followed_by_no_download(script, tmpdir): - """ - Test installing an editable in two steps (first with --no-install, then with --no-download). -@@ -113,6 +119,7 @@ def test_editable_no_install_followed_by - result.assert_installed('INITools', without_files=[curdir, '.svn']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_no_install_followed_by_no_download(script): - """ - Test installing in two steps (first with --no-install, then with --no-download). -@@ -143,6 +150,7 @@ def test_bad_install_with_no_download(sc - "an equivalent install with --no-install?" in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_dev_version_from_pypi(script): - """ - Test using package==dev. -@@ -154,6 +162,7 @@ def test_install_dev_version_from_pypi(s - assert (script.site_packages / 'initools') in result.files_created, str(result.stdout) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_editable_from_git(script, tmpdir): - """ - Test cloning from Git. -@@ -166,6 +175,7 @@ def test_install_editable_from_git(scrip - result.assert_installed('pip-test-package', with_files=['.git']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_editable_from_hg(script, tmpdir): - """ - Test cloning from Mercurial. -@@ -177,6 +187,7 @@ def test_install_editable_from_hg(script - result.assert_installed('ScriptTest', with_files=['.hg']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_vcs_url_final_slash_normalization(script, tmpdir): - """ - Test that presence or absence of final slash in VCS URL is normalized. -@@ -188,6 +199,7 @@ def test_vcs_url_final_slash_normalizati - assert 'pip-log.txt' not in result.files_created, result.files_created['pip-log.txt'].bytes - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_editable_from_bazaar(script, tmpdir): - """ - Test checking out from Bazaar. -@@ -199,6 +211,7 @@ def test_install_editable_from_bazaar(sc - result.assert_installed('django-wikiapp', with_files=['.bzr']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_vcs_url_urlquote_normalization(script, tmpdir): - """ - Test that urlquoted characters are normalized for repo URL comparison. -@@ -295,6 +308,7 @@ def test_install_pardir(script, data): - assert egg_info_folder in result.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_global_option(script): - """ - Test using global distutils options. -@@ -320,6 +334,7 @@ def test_install_with_hacked_egg_info(sc - assert 'Successfully installed hackedegginfo\n' in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_using_install_option_and_editable(script, tmpdir): - """ - Test installing a tool using -e and --install-option -@@ -334,6 +349,7 @@ def test_install_using_install_option_an - assert virtualenv_bin in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_global_option_using_editable(script, tmpdir): - """ - Test using global distutils options, but in an editable installation -@@ -345,6 +361,7 @@ def test_install_global_option_using_edi - assert '0.2.5\n' in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_package_with_same_name_in_curdir(script): - """ - Test installing a package with the same name of a local folder -@@ -398,6 +415,7 @@ def test_install_folder_using_relative_p - assert egg_folder in result.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_package_which_contains_dev_in_name(script): - """ - Test installing package from pypi which contains 'dev' in name -@@ -409,6 +427,7 @@ def test_install_package_which_contains_ - assert egg_info_folder in result.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_package_with_target(script): - """ - Test installing a package using pip install --target -@@ -481,6 +500,7 @@ def test_url_req_case_mismatch(script, d - assert egg_folder not in result.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_compiles_pyc(script): - """ - Test installing with --compile on -@@ -501,6 +521,7 @@ def test_compiles_pyc(script): - assert any(exists) - - -+@pytest.mark.skipif(True, reason='No network') - def test_no_compiles_pyc(script, data): - """ - Test installing from wheel with --compile on -diff -up pip-1.5.6/tests/functional/test_install_reqs.py.orig pip-1.5.6/tests/functional/test_install_reqs.py ---- pip-1.5.6/tests/functional/test_install_reqs.py.orig 2014-11-27 11:23:21.114672571 +0100 -+++ pip-1.5.6/tests/functional/test_install_reqs.py 2014-11-27 14:25:42.443560282 +0100 -@@ -13,6 +13,7 @@ from tests.lib.local_repos import local_ - from tests.lib.path import Path - - -+@pytest.mark.skipif(True, reason='No network') - def test_requirements_file(script): - """ - Test installing from a requirements file. -@@ -59,6 +60,7 @@ def test_relative_requirements_file(scri - assert (script.site_packages/'fspkg') in result.files_created, str(result.stdout) - - -+@pytest.mark.skipif(True, reason='No network') - def test_multiple_requirements_files(script, tmpdir): - """ - Test installing from multiple nested requirements files. -@@ -110,6 +112,7 @@ def test_install_local_editable_with_ext - assert script.site_packages/'simple' in res.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_local_editable_with_subdirectory(script): - version_pkg_path = _create_test_package_with_subdirectory(script, - 'version_subpkg') -diff -up pip-1.5.6/tests/functional/test_install_upgrade.py.orig pip-1.5.6/tests/functional/test_install_upgrade.py ---- pip-1.5.6/tests/functional/test_install_upgrade.py.orig 2014-11-27 11:23:21.159672835 +0100 -+++ pip-1.5.6/tests/functional/test_install_upgrade.py 2014-11-27 11:23:21.743676253 +0100 -@@ -22,6 +22,7 @@ def test_no_upgrade_unless_requested(scr - assert not result.files_created, 'pip install INITools upgraded when it should not have' - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_to_specific_version(script): - """ - It does upgrade to specific version requested. -@@ -34,6 +35,7 @@ def test_upgrade_to_specific_version(scr - assert script.site_packages/'INITools-0.2-py%s.egg-info' % pyversion in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_if_requested(script): - """ - And it does upgrade if requested. -@@ -56,6 +58,7 @@ def test_upgrade_with_newest_already_ins - assert 'already up-to-date' in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_force_reinstall_newest(script): - """ - Force reinstallation of a package even if it is already at its newest -@@ -69,6 +72,7 @@ def test_upgrade_force_reinstall_newest( - assert_all_changes(result, result3, [script.venv/'build', 'cache']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_before_upgrade(script): - """ - Automatic uninstall-before-upgrade. -@@ -82,6 +86,7 @@ def test_uninstall_before_upgrade(script - assert_all_changes(result, result3, [script.venv/'build', 'cache']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_before_upgrade_from_url(script): - """ - Automatic uninstall-before-upgrade from URL. -@@ -95,6 +100,7 @@ def test_uninstall_before_upgrade_from_u - assert_all_changes(result, result3, [script.venv/'build', 'cache']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_to_same_version_from_url(script): - """ - When installing from a URL the same version that is already installed, no -@@ -109,6 +115,7 @@ def test_upgrade_to_same_version_from_ur - assert_all_changes(result, result3, [script.venv/'build', 'cache']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_from_reqs_file(script): - """ - Upgrade from a requirements file. -@@ -161,6 +168,7 @@ def test_editable_git_upgrade(script): - assert 'some different version' in version2.stdout, "Output: %s" % (version2.stdout) - - -+@pytest.mark.skipif(True, reason='No network') - def test_should_not_install_always_from_cache(script): - """ - If there is an old cached package, pip should download the newer version -@@ -173,6 +181,7 @@ def test_should_not_install_always_from_ - assert script.site_packages/'INITools-0.1-py%s.egg-info' % pyversion in result.files_created - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_with_ignoreinstalled_requested(script): - """ - Test old conflicting package is completely ignored -@@ -185,6 +194,7 @@ def test_install_with_ignoreinstalled_re - assert os.path.exists(script.site_packages_path/'INITools-0.3-py%s.egg-info' % pyversion) - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_vcs_req_with_no_dists_found(script, tmpdir): - """It can upgrade a VCS requirement that has no distributions otherwise.""" - req = "%s#egg=pip-test-package" % local_checkout( -@@ -194,6 +204,7 @@ def test_upgrade_vcs_req_with_no_dists_f - assert not result.returncode - - -+@pytest.mark.skipif(True, reason='No network') - def test_upgrade_vcs_req_with_dist_found(script): - """It can upgrade a VCS requirement that has distributions on the index.""" - # TODO(pnasrat) Using local_checkout fails on windows - oddness with the test path urls/git. -diff -up pip-1.5.6/tests/functional/test_install_user.py.orig pip-1.5.6/tests/functional/test_install_user.py ---- pip-1.5.6/tests/functional/test_install_user.py.orig 2014-11-27 11:23:21.750676293 +0100 -+++ pip-1.5.6/tests/functional/test_install_user.py 2014-11-27 11:23:22.157678676 +0100 -@@ -42,7 +42,8 @@ def _patch_dist_in_site_packages(script) - @pytest.mark.skipif("hasattr(sys, 'pypy_version_info')") - class Tests_UserSite: - -- def test_reset_env_system_site_packages_usersite(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_reset_env_system_site_packages_usersite(self, script, virtualenv): - """ - reset_env(system_site_packages=True) produces env where a --user install can be found using pkg_resources - """ -@@ -53,7 +54,8 @@ class Tests_UserSite: - assert 'INITools'== project_name, "'%s' should be 'INITools'" %project_name - - -- def test_install_subversion_usersite_editable_with_distribute(self, script, virtualenv, tmpdir): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_install_subversion_usersite_editable_with_distribute(self, script, virtualenv, tmpdir): - """ - Test installing current directory ('.') into usersite after installing distribute - """ -@@ -85,7 +87,8 @@ class Tests_UserSite: - result = script.pip('install', '--user', curdir, cwd=run_from, expect_error=True) - assert "Can not perform a '--user' install. User site-packages are not visible in this virtualenv." in result.stdout - -- def test_install_user_conflict_in_usersite(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_install_user_conflict_in_usersite(self, script, virtualenv): - """ - Test user install with conflict in usersite updates usersite. - """ -@@ -99,7 +102,8 @@ class Tests_UserSite: - assert egg_info_folder in result2.files_created, str(result2) - assert not isfile(initools_v3_file), initools_v3_file - -- def test_install_user_conflict_in_globalsite(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_install_user_conflict_in_globalsite(self, script, virtualenv): - """ - Test user install with conflict in global site ignores site and installs to usersite - """ -@@ -129,7 +133,8 @@ class Tests_UserSite: - assert isdir(egg_info_folder) - assert isdir(initools_folder) - -- def test_upgrade_user_conflict_in_globalsite(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_upgrade_user_conflict_in_globalsite(self, script, virtualenv): - """ - Test user install/upgrade with conflict in global site ignores site and installs to usersite - """ -@@ -159,7 +164,8 @@ class Tests_UserSite: - assert isdir(egg_info_folder), result2.stdout - assert isdir(initools_folder) - -- def test_install_user_conflict_in_globalsite_and_usersite(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_install_user_conflict_in_globalsite_and_usersite(self, script, virtualenv): - """ - Test user install with conflict in globalsite and usersite ignores global site and updates usersite. - """ -@@ -190,7 +196,8 @@ class Tests_UserSite: - assert isdir(egg_info_folder) - assert isdir(initools_folder) - -- def test_install_user_in_global_virtualenv_with_conflict_fails(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_install_user_in_global_virtualenv_with_conflict_fails(self, script, virtualenv): - """ - Test user install in --system-site-packages virtualenv with conflict in site fails. - """ -diff -up pip-1.5.6/tests/functional/test_install_vcs_git.py.orig pip-1.5.6/tests/functional/test_install_vcs_git.py ---- pip-1.5.6/tests/functional/test_install_vcs_git.py.orig 2014-11-27 14:27:33.238340755 +0100 -+++ pip-1.5.6/tests/functional/test_install_vcs_git.py 2014-11-27 14:28:28.154727603 +0100 -@@ -36,6 +36,7 @@ def test_get_refs_should_return_branch_n - assert result['branch0.1'] == commit, result - - -+@pytest.mark.skipif(True, reason='No network') - def test_get_refs_should_ignore_no_branch(script): - version_pkg_path = _create_test_package(script) - script.run('git', 'branch', 'branch0.1', cwd=version_pkg_path) -@@ -79,6 +80,7 @@ def test_check_rev_options_should_handle - - # TODO(pnasrat) fix all helpers to do right things with paths on windows. - @pytest.mark.skipif("sys.platform == 'win32'") -+@pytest.mark.skipif(True, reason='No network') - def test_check_submodule_addition(script): - """ - Submodules are pulled in on install and updated on upgrade. -diff -up pip-1.5.6/tests/functional/test_install_vcs.py.orig pip-1.5.6/tests/functional/test_install_vcs.py ---- pip-1.5.6/tests/functional/test_install_vcs.py.orig 2014-11-27 11:23:22.169678746 +0100 -+++ pip-1.5.6/tests/functional/test_install_vcs.py 2014-11-27 14:27:14.133206173 +0100 -@@ -1,7 +1,9 @@ -+import pytest - from tests.lib import _create_test_package, _change_test_package_version - from tests.lib.local_repos import local_checkout - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_editable_from_git_with_https(script, tmpdir): - """ - Test cloning from Git with https. -@@ -13,6 +15,7 @@ def test_install_editable_from_git_with_ - result.assert_installed('pip-test-package', with_files=['.git']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_sha1_revisions(script): - """ - Git backend should be able to install from SHA1 revisions -@@ -25,6 +28,7 @@ def test_git_with_sha1_revisions(script) - assert '0.1' in version.stdout, version.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_branch_name_as_revision(script): - """ - Git backend should be able to install from branch names -@@ -37,6 +41,7 @@ def test_git_with_branch_name_as_revisio - assert 'some different version' in version.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_tag_name_as_revision(script): - """ - Git backend should be able to install from tag names -@@ -49,6 +54,7 @@ def test_git_with_tag_name_as_revision(s - assert '0.1' in version.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_tag_name_and_update(script, tmpdir): - """ - Test cloning a git repository and updating to a different version. -@@ -64,6 +70,7 @@ def test_git_with_tag_name_and_update(sc - assert '0.1.2' in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_branch_should_not_be_changed(script, tmpdir): - """ - Editable installations should not change branch -@@ -77,6 +84,7 @@ def test_git_branch_should_not_be_change - assert '* master' in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_non_editable_unpacking(script, tmpdir): - """ - Test cloning a git repository from a non-editable URL with a given tag. -@@ -88,6 +96,7 @@ def test_git_with_non_editable_unpacking - assert '0.1.2' in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_editable_where_egg_contains_dev_string(script, tmpdir): - """ - Test cloning a git repository from an editable url which contains "dev" string -@@ -97,6 +106,7 @@ def test_git_with_editable_where_egg_con - result.assert_installed('django-devserver', with_files=['.git']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_non_editable_where_egg_contains_dev_string(script, tmpdir): - """ - Test cloning a git repository from a non-editable url which contains "dev" string -@@ -107,6 +117,7 @@ def test_git_with_non_editable_where_egg - assert devserver_folder in result.files_created, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_with_ambiguous_revs(script): - """ - Test git with two "names" (tag/branch) pointing to the same commit -@@ -121,6 +132,7 @@ def test_git_with_ambiguous_revs(script) - result.assert_installed('version-pkg', with_files=['.git']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_git_works_with_editable_non_origin_repo(script): - # set up, create a git repo and install it as editable from a local directory path - version_pkg_path = _create_test_package(script) -diff -up pip-1.5.6/tests/functional/test_install_vcs_svn.py.orig pip-1.5.6/tests/functional/test_install_vcs_svn.py ---- pip-1.5.6/tests/functional/test_install_vcs_svn.py.orig 2014-11-27 14:30:00.551378474 +0100 -+++ pip-1.5.6/tests/functional/test_install_vcs_svn.py 2014-11-27 14:30:03.217397254 +0100 -@@ -1,7 +1,8 @@ -+import pytest - from mock import patch - from pip.vcs.subversion import Subversion - -- -+@pytest.mark.skipif(True, reason='No network') - @patch('pip.vcs.subversion.call_subprocess') - def test_obtain_should_recognize_auth_info_url(call_subprocess_mock, script): - svn = Subversion(url='svn+http://username:password@svn.example.com/') -@@ -11,6 +12,7 @@ def test_obtain_should_recognize_auth_in - 'http://username:password@svn.example.com/', script.scratch_path/'test']) - - -+@pytest.mark.skipif(True, reason='No network') - @patch('pip.vcs.subversion.call_subprocess') - def test_export_should_recognize_auth_info_url(call_subprocess_mock, script): - svn = Subversion(url='svn+http://username:password@svn.example.com/') -diff -up pip-1.5.6/tests/functional/test_install_wheel.py.orig pip-1.5.6/tests/functional/test_install_wheel.py ---- pip-1.5.6/tests/functional/test_install_wheel.py.orig 2014-11-27 11:23:22.371679928 +0100 -+++ pip-1.5.6/tests/functional/test_install_wheel.py 2014-11-27 11:23:22.372679934 +0100 -@@ -92,6 +92,7 @@ def test_install_from_wheel_with_headers - result.stdout) - - -+@pytest.mark.skipif(True, reason='No network') - def test_install_wheel_with_target(script, data): - """ - Test installing a wheel using pip install --target -diff -up pip-1.5.6/tests/functional/test_list.py.orig pip-1.5.6/tests/functional/test_list.py ---- pip-1.5.6/tests/functional/test_list.py.orig 2014-11-27 11:23:22.379679974 +0100 -+++ pip-1.5.6/tests/functional/test_list.py 2014-11-27 11:31:06.129431433 +0100 -@@ -1,6 +1,7 @@ - import os - import re - import textwrap -+import pytest - - from tests.lib.local_repos import local_checkout - -@@ -26,6 +27,7 @@ def test_local_flag(script, data): - assert 'simple (1.0)' in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_uptodate_flag(script, data): - """ - Test the behavior of --uptodate flag in the list command -@@ -39,6 +41,7 @@ def test_uptodate_flag(script, data): - assert 'simple2 (3.0)' in result.stdout, str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_outdated_flag(script, data): - """ - Test the behavior of --outdated flag in the list command -@@ -52,6 +55,7 @@ def test_outdated_flag(script, data): - assert 'simple2' not in result.stdout, str(result) #3.0 is latest - - -+@pytest.mark.skipif(True, reason='No network') - def test_editables_flag(script, data): - """ - Test the behavior of --editables flag in the list command -diff -up pip-1.5.6/tests/functional/test_search.py.orig pip-1.5.6/tests/functional/test_search.py ---- pip-1.5.6/tests/functional/test_search.py.orig 2014-11-27 11:23:22.903683041 +0100 -+++ pip-1.5.6/tests/functional/test_search.py 2014-11-27 11:30:57.397378486 +0100 -@@ -1,3 +1,4 @@ -+import pytest - import pip.download - from pip.commands.search import (compare_versions, - highest_version, -@@ -53,6 +54,7 @@ def test_invalid_pypi_transformation(): - assert transform_hits(pypi_hits) == expected - - -+@pytest.mark.skipif(True, reason='No network') - def test_search(script): - """ - End to end test of search command. -@@ -62,6 +64,7 @@ def test_search(script): - assert 'A tool for installing and managing Python packages' in output.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_multiple_search(script): - """ - Test searching for multiple packages at once. -@@ -80,6 +83,7 @@ def test_search_missing_argument(script) - assert 'ERROR: Missing required argument (search query).' in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_run_method_should_return_sucess_when_find_packages(): - """ - Test SearchCommand.run for found package -@@ -91,6 +95,7 @@ def test_run_method_should_return_sucess - assert status == SUCCESS - - -+@pytest.mark.skipif(True, reason='No network') - def test_run_method_should_return_no_matches_found_when_does_not_find_packages(): - """ - Test SearchCommand.run for no matches -@@ -102,6 +107,7 @@ def test_run_method_should_return_no_mat - assert status == NO_MATCHES_FOUND, status - - -+@pytest.mark.skipif(True, reason='No network') - def test_search_should_exit_status_code_zero_when_find_packages(script): - """ - Test search exit status code for package found -@@ -110,6 +116,7 @@ def test_search_should_exit_status_code_ - assert result.returncode == SUCCESS - - -+@pytest.mark.skipif(True, reason='No network') - def test_search_exit_status_code_when_finds_no_package(script): - """ - Test search exit status code for no matches -diff -up pip-1.5.6/tests/functional/test_show.py.orig pip-1.5.6/tests/functional/test_show.py ---- pip-1.5.6/tests/functional/test_show.py.orig 2014-11-27 11:23:23.160684545 +0100 -+++ pip-1.5.6/tests/functional/test_show.py 2014-11-27 11:30:52.417348289 +0100 -@@ -1,4 +1,5 @@ - import re -+import pytest - from pip import __version__ - from pip.commands.show import search_packages_info - -@@ -34,6 +35,7 @@ def test_show_with_files_not_found(scrip - assert lines[6] == 'Cannot locate installed-files.txt', lines[5] - - -+@pytest.mark.skipif(True, reason='No network') - def test_show_with_all_files(script): - """ - Test listing all files in the show command. -diff -up pip-1.5.6/tests/functional/test_uninstall.py.orig pip-1.5.6/tests/functional/test_uninstall.py ---- pip-1.5.6/tests/functional/test_uninstall.py.orig 2014-11-27 11:23:23.168684592 +0100 -+++ pip-1.5.6/tests/functional/test_uninstall.py 2014-11-27 11:30:22.367166080 +0100 -@@ -3,6 +3,7 @@ from __future__ import with_statement - import textwrap - import os - import sys -+import pytest - from os.path import join, abspath, normpath - from tempfile import mkdtemp - from mock import patch -@@ -12,6 +13,7 @@ from tests.lib.local_repos import local_ - from pip.util import rmtree - - -+@pytest.mark.skipif(True, reason='No network') - def test_simple_uninstall(script): - """ - Test simple install and uninstall. -@@ -25,6 +27,7 @@ def test_simple_uninstall(script): - assert_all_changes(result, result2, [script.venv/'build', 'cache']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_with_scripts(script): - """ - Uninstall an easy_installed package with scripts. -@@ -38,6 +41,7 @@ def test_uninstall_with_scripts(script): - assert_all_changes(result, result2, [script.venv/'build', 'cache', easy_install_pth]) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_easy_install_after_import(script): - """ - Uninstall an easy_installed package after it's been imported -@@ -50,6 +54,7 @@ def test_uninstall_easy_install_after_im - assert_all_changes(result, result2, [script.venv/'build', 'cache', script.site_packages/'easy-install.pth']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_namespace_package(script): - """ - Uninstall a distribution with a namespace package without clobbering -@@ -90,6 +95,7 @@ def test_uninstall_overlapping_package(s - assert_all_changes(result2, result3, []) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_console_scripts(script): - """ - Test uninstalling a package with more files (console_script entry points, extra directories). -@@ -102,6 +108,7 @@ def test_uninstall_console_scripts(scrip - assert_all_changes(result, result2, [script.venv/'build', 'cache']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_easy_installed_console_scripts(script): - """ - Test uninstalling package with console_scripts that is easy_installed. -@@ -114,6 +121,7 @@ def test_uninstall_easy_installed_consol - assert_all_changes(result, result2, [script.venv/'build', 'cache', script.site_packages/'easy-install.pth']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_editable_from_svn(script, tmpdir): - """ - Test uninstalling an editable installation from svn. -@@ -126,6 +134,7 @@ def test_uninstall_editable_from_svn(scr - assert_all_changes(result, result2, [script.venv/'src', script.venv/'build', script.site_packages/'easy-install.pth']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_editable_with_source_outside_venv(script, tmpdir): - """ - Test uninstalling editable install from existing source outside the venv. -@@ -149,6 +158,7 @@ def _test_uninstall_editable_with_source - assert_all_changes(result, result3, [script.venv/'build', script.site_packages/'easy-install.pth']) - - -+@pytest.mark.skipif(True, reason='No network') - def test_uninstall_from_reqs_file(script, tmpdir): - """ - Test uninstall from a requirements file. -diff -up pip-1.5.6/tests/functional/test_uninstall_user.py.orig pip-1.5.6/tests/functional/test_uninstall_user.py ---- pip-1.5.6/tests/functional/test_uninstall_user.py.orig 2014-11-27 11:23:23.590687062 +0100 -+++ pip-1.5.6/tests/functional/test_uninstall_user.py 2014-11-27 11:29:55.413002643 +0100 -@@ -17,7 +17,8 @@ from tests.functional.test_install_user - @pytest.mark.skipif("hasattr(sys, 'pypy_version_info')") - class Tests_UninstallUserSite: - -- def test_uninstall_from_usersite(self, script, virtualenv): -+ @pytest.mark.skipif(True, reason='No network') -+ def test_uninstall_from_usersite(self, script, virtualenv): - """ - Test uninstall from usersite - """ -diff -up pip-1.5.6/tests/functional/test_wheel.py.orig pip-1.5.6/tests/functional/test_wheel.py ---- pip-1.5.6/tests/functional/test_wheel.py.orig 2014-11-27 11:23:23.598687109 +0100 -+++ pip-1.5.6/tests/functional/test_wheel.py 2014-11-27 11:30:30.345214455 +0100 -@@ -2,6 +2,7 @@ - import os - import sys - import textwrap -+import pytest - - from os.path import exists - -@@ -20,6 +21,7 @@ def test_pip_wheel_fails_without_wheel(s - assert "'pip wheel' requires the 'wheel' package" in result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_wheel_success(script, data): - """ - Test 'pip wheel' success. -@@ -32,6 +34,7 @@ def test_pip_wheel_success(script, data) - assert "Successfully built simple" in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_wheel_downloads_wheels(script, data): - """ - Test 'pip wheel' downloads wheels -@@ -46,6 +49,7 @@ def test_pip_wheel_downloads_wheels(scri - assert "Saved" in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_wheel_fail(script, data): - """ - Test 'pip wheel' failure. -@@ -59,6 +63,7 @@ def test_pip_wheel_fail(script, data): - assert "Failed to build wheelbroken" in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_wheel_ignore_wheels_editables(script, data): - """ - Test 'pip wheel' ignores editables -@@ -85,6 +90,7 @@ def test_pip_wheel_ignore_wheels_editabl - assert ignore_editable in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_no_clean_option_blocks_cleaning_after_wheel(script, data): - """ - Test --no-clean option blocks cleaning after wheel build -@@ -95,6 +101,7 @@ def test_no_clean_option_blocks_cleaning - assert exists(build), "build/simple should still exist %s" % str(result) - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_wheel_source_deps(script, data): - """ - Test 'pip wheel --use-wheel' finds and builds source archive dependencies of wheels -@@ -108,6 +115,7 @@ def test_pip_wheel_source_deps(script, d - assert "Successfully built source" in result.stdout, result.stdout - - -+@pytest.mark.skipif(True, reason='No network') - def test_pip_wheel_fail_cause_of_previous_build_dir(script, data): - """Test when 'pip wheel' tries to install a package that has a previous build directory""" - -diff -up pip-1.5.6/tests/unit/test_finder.py.orig pip-1.5.6/tests/unit/test_finder.py ---- pip-1.5.6/tests/unit/test_finder.py.orig 2014-11-27 11:23:23.937689093 +0100 -+++ pip-1.5.6/tests/unit/test_finder.py 2014-11-27 14:30:48.597716927 +0100 -@@ -71,6 +71,7 @@ def test_finder_detects_latest_already_s - finder.find_requirement(req, True) - - -+@pytest.mark.skipif(True, reason='No network') - def test_finder_detects_latest_already_satisfied_pypi_links(): - """Test PackageFinder detects latest already satisified using pypi links""" - req = InstallRequirement.from_line('initools', None) -@@ -199,6 +200,7 @@ def test_finder_priority_file_over_page( - assert link.url.startswith("file://") - - -+@pytest.mark.skipif(True, reason='No network') - def test_finder_priority_page_over_deplink(): - """Test PackageFinder prefers page links over equivalent dependency links""" - req = InstallRequirement.from_line('gmpy==1.15', None) -diff -up pip-1.5.6/tests/unit/test_req.py.orig pip-1.5.6/tests/unit/test_req.py ---- pip-1.5.6/tests/unit/test_req.py.orig 2014-11-27 11:23:23.946689145 +0100 -+++ pip-1.5.6/tests/unit/test_req.py 2014-11-27 11:23:23.947689151 +0100 -@@ -149,6 +149,7 @@ def test_parse_editable_local_extras(isd - normcase_mock.return_value = "/some/path/foo" - assert parse_editable('foo[bar,baz]', 'git') == (None, 'file:///some/path/foo', ('bar', 'baz')) - -+@pytest.mark.skipif(True, reason='No network') - def test_remote_reqs_parse(): - """ - Test parsing a simple remote requirements file -diff -up pip-1.5.6/tests/functional/test_install_cleanup.py.orig pip-1.5.6/tests/functional/test_install_cleanup.py ---- pip-1.5.6/tests/functional/test_install_cleanup.py.orig 2014-12-01 13:31:24.075490830 +0100 -+++ pip-1.5.6/tests/functional/test_install_cleanup.py 2014-12-01 13:30:59.208326741 +0100 -@@ -62,6 +62,7 @@ def test_cleanup_after_install_from_loca - script.assert_no_temp() - - -+@pytest.mark.skipif(True, reason='No network') - def test_no_install_and_download_should_not_leave_build_dir(script): - """ - It should remove build/ dir if it was pip that created -diff -up pip-1.5.6/tests/functional/test_install_vcs_git.py.orig pip-1.5.6/tests/functional/test_install_vcs_git.py ---- pip-1.5.6/tests/functional/test_install_vcs_git.py.orig 2014-12-01 13:32:11.419803237 +0100 -+++ pip-1.5.6/tests/functional/test_install_vcs_git.py 2014-12-01 13:32:30.298927814 +0100 -@@ -13,6 +13,7 @@ from tests.lib.git_submodule_helpers imp - ) - - -+@pytest.mark.skipif(True, reason='No network') - def test_get_refs_should_return_tag_name_and_commit_pair(script): - version_pkg_path = _create_test_package(script) - script.run('git', 'tag', '0.1', cwd=version_pkg_path) -@@ -25,6 +26,7 @@ def test_get_refs_should_return_tag_name - assert result['0.2'] == commit, result - - -+@pytest.mark.skipif(True, reason='No network') - def test_get_refs_should_return_branch_name_and_commit_pair(script): - version_pkg_path = _create_test_package(script) - script.run('git', 'branch', 'branch0.1', cwd=version_pkg_path) diff --git a/sources b/sources index fd27b5b..48867fb 100644 --- a/sources +++ b/sources @@ -1,3 +1,5 @@ -SHA512 (pip-19.0.3.tar.gz) = f56df1e2fe18eccc4dcba66b0a93a933eb94d12ed0f31d3923662fb85075192674c98bbc00dd48185b1d87cf28a64a6dc321de524e4ccb39a8877b3d5cf578e1 -SHA512 (d2e63fbfc62af3b7050f619b2f5bb8658985b931.tar.gz) = fc7b11c5cbf6322469ce2eaca2a8d7eb60b17398d316f7465ab5d3d38dabd00ee22a3da7437a28f6312f0115f77f2df0d8bf0abc671e055eef06356c94283409 -SHA512 (2018.2.tar.gz) = 4c09c43a70ecb3ca3bc9445b01bf209eb382e41d9c969145696dea38551992ed88fd9b725a1264380f3dbdf8acdaf5ada3ef86b44255cdfbdbe4a01a1630912d +SHA512 (pip-25.3.tar.gz) = f50db092213ec3bb819d3da5669f73d119b5ec7f7ac5e8a587a17c27eafa32bc17a057df09389c526a3769ef3577f5553187d54ceffa89aed63f4b4498ff044e +SHA512 (setuptools-79.0.1-py3-none-any.whl) = fef6cfc6f95a5bb7320f1680e1c665cb8d9a4e4227cde4d8aab8a50bed4bcf04320085b9d7d5343359f887008db5c5a861e57f3d08b7b0b2311a28adaeee6b4a +SHA512 (wheel-0.45.1-py3-none-any.whl) = 86c16248ec804ee0ac95d43b03d47351dceb534d0cdc4025ca1eb073e39e539de44c870b9261f0373144e1537f0e42675a759a318a8d5d346bbd9efcb704061d +SHA512 (flit_core-3.12.0-py3-none-any.whl) = 790c12b1f43201e365fb3f8f2f0a54e1a578876799dfdf8bfeea679a25ea096bf62946d006618c1458ae6e37ce6d00998f37e9aba426d5ab80d32ef2d75da4e0 +SHA512 (coverage-0-py3-none-any.whl) = e734192565347010efe68f8ba600254259c9b647f3c553fd4e5d87b1d7f955cb15d6f7d807716f4a6415d239beed945fbec7210feaf502e9cc849c332845926e diff --git a/tests/bash_completion/main.fmf b/tests/bash_completion/main.fmf new file mode 100644 index 0000000..cde6f68 --- /dev/null +++ b/tests/bash_completion/main.fmf @@ -0,0 +1,35 @@ +summary: PIP bash completion functionality smoke test +description: | + Comprehensive test for pip bash completion functionality on Fedora/RHEL systems. + + The test performs the following steps: + 1. Finds the bash completion script in the given (e.g. python3-pip) RPM package + 2. Discovers all pip executables in the package (e.g. /usr/bin/pip and /usr/bin/pip3.14) + 3. Sources the completion script and verifies completion for all executables is registered + 4. Runs functional TAB completion tests using expect (for regular and POSIX mode of Bash) + 5. Validates that completion works for basic pip commands + + This is a smoke test to ensure pip bash completion is properly + installed and functional after package installation. + +component: + - python3-pip + +test: ./pip_completion_full_test.sh + +framework: shell + +duration: 5m +tier: 1 + + +require: + - python3-pip + - bash-completion + - expect + - rpm + - bash + + +environment: + PACKAGE: python3-pip diff --git a/tests/bash_completion/pip_completion_full_test.sh b/tests/bash_completion/pip_completion_full_test.sh new file mode 100755 index 0000000..94fcdb9 --- /dev/null +++ b/tests/bash_completion/pip_completion_full_test.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# Comprehensive PIP bash completion test for RHEL +# Finds completion scripts from RPM, tests all pip binaries, and runs functional tests + +PACKAGE="${PACKAGE:-python3-pip}" + + +# Step 1: Find bash completion scripts in python3-pip RPM package +echo "Step 1: Finding bash completion scripts in $PACKAGE RPM package..." + +COMPLETION_FILE=$(rpm -ql $PACKAGE 2>/dev/null | grep -E "/usr/share/bash-completion/completions/" || true) + +if [[ -z "$COMPLETION_FILE" ]]; then + echo "✗ No bash completion files found in $PACKAGE package" + exit 1 +fi + +# Check if there's exactly one completion file +COMPLETION_FILE_COUNT=$(echo "$COMPLETION_FILE" | wc -l) + +if [[ $COMPLETION_FILE_COUNT -gt 1 ]]; then + echo "✗ Multiple bash completion files found in $PACKAGE package:" + echo "$COMPLETION_FILE" | sed 's/^/ - /' + echo "Expected exactly one completion file, found $COMPLETION_FILE_COUNT" + exit 1 +fi + + +echo "✓ Found completion file from $PACKAGE package:" +echo "$COMPLETION_FILE" | sed 's/^/ - /' + +# Step 2: Find all pip binaries +echo +echo "Step 2: Finding all pip binaries..." +PIP_BINARIES=() +PIP_FILES=$(rpm -ql $PACKAGE | grep /bin/p) +for pip_file in $PIP_FILES; do + if [[ -x "$pip_file" ]]; then + pip_cmd=$(basename "$pip_file") + PIP_BINARIES+=("$pip_cmd") + echo "✓ Found: $pip_cmd -> $pip_file" + fi +done + +if [[ ${#PIP_BINARIES[@]} -eq 0 ]]; then + echo "✗ No pip binaries found" + exit 1 +fi + +echo "Total pip binaries found: ${#PIP_BINARIES[@]}" + +# Step 3: Source completion scripts and test each binary +echo +echo "Step 3: Testing completion for each pip binary..." + +for AS_POSIX in 0 1; do + if [[ $AS_POSIX -eq 1 ]]; then + echo "Testing in POSIX mode" + POSIX="--posix" + else + echo "Testing in non-POSIX mode" + POSIX="" + fi + + echo "Sourcing: $COMPLETION_FILE" + if bash --norc $POSIX -c "source $COMPLETION_FILE" ; then + echo "✓ Successfully sourced $COMPLETION_FILE" + else + echo "! Warning: Failed to source $COMPLETION_FILE" + exit 1 + fi + + export AS_POSIX + for pip_exec in "${PIP_BINARIES[@]}"; do + echo "Running expect test with $COMPLETION_FILE and $pip_exec..." + if ./test_pip_completion.exp "$COMPLETION_FILE" "$pip_exec"; then + echo "✓ Functional test passed" + else + echo "✗ Functional test failed" + exit 1 + fi + done + +done + + +echo "✓ All tests completed successfully!" diff --git a/tests/bash_completion/test_pip_completion.exp b/tests/bash_completion/test_pip_completion.exp new file mode 100755 index 0000000..4389174 --- /dev/null +++ b/tests/bash_completion/test_pip_completion.exp @@ -0,0 +1,117 @@ +#!/usr/bin/expect -f + +# PIP bash completion smoke test using expect +# Tests actual TAB completion functionality +# Usage: test_pip_completion.exp [completion_file] [pip_binary] + +set timeout 5 + + +set completion_file [lindex $argv 0] +set pip_exec [lindex $argv 1] + + +puts "=== PIP Bash Completion Test (using expect) ===" +puts "Testing completion file: $completion_file" +puts "Testing pip binary: $pip_exec" + +# Check if completion file exists first +if {![file exists $completion_file]} { + puts "✗ Completion file not found: $completion_file" + exit 1 +} +puts "✓ Completion file found: $completion_file" + +# Start bash shell +if {[info exists env(AS_POSIX)] && $env(AS_POSIX) == "1"} { + spawn bash --norc --posix +} else { + spawn bash --norc +} +expect "$ " + +# Source the completion file +send "source $completion_file\r" +expect "$ " +puts "Attempted to source completion file" + +# Test 1: Basic pip command completion +puts "\nTest 1: Testing '$pip_exec ' + TAB completion..." +send "$pip_exec " +sleep 0.1 +# Send TAB TAB using hex codes +send "\x09\x09" +expect { + -re "(install|uninstall|list|show)" { + puts "✓ Basic pip commands found in completion" + expect "$ " + } + -re "Display all" { + puts "✓ Completion showing options menu" + send "n\r" + expect "$ " + } + timeout { + puts "✗ Timeout waiting for completion - test failed" + exit 1 + } +} + +# Clear the line and ensure clean prompt +send "\x03" +expect "$ " +send "\r" +expect "$ " + +# Test 2: Test partial command completion (simpler test) +puts "\nTest 2: Testing '$pip_exec insta' + TAB completion..." +send "$pip_exec insta" +sleep 0.1 +# Single TAB for completion +send "\x09" +expect { + -re "install" { + puts "✓ Partial command completion works (insta -> install)" + expect "$ " + } + timeout { + puts "✗ Timeout on partial completion test - test failed" + exit 1 + } +} + +# Clear the line and ensure clean prompt +send "\x03" +expect "$ " +send "\r" +expect "$ " + +# Test 3: Test help completion +puts "\nTest 3: Testing '$pip_exec --' + TAB completion..." +send "$pip_exec --" +sleep 0.1 +send "\x09\x09" +expect { + -re "(--help|--version)" { + puts "✓ Command options found in completion" + expect "$ " + } + timeout { + puts "✗ Timeout on options completion test - test failed" + exit 1 + } +} + +# Final cleanup - make sure we're at clean prompt +send "\x03" +expect "$ " +send "\r" +expect "$ " + +# Exit bash cleanly +send "exit\r" +expect eof + +puts "\n=== Completion Test Complete ===" +puts "PIP bash completion functionality tested!" + diff --git a/tests/pip_install_upgrade/runtest.sh b/tests/pip_install_upgrade/runtest.sh new file mode 100755 index 0000000..4c520f5 --- /dev/null +++ b/tests/pip_install_upgrade/runtest.sh @@ -0,0 +1,40 @@ +#!/bin/sh -eux +# This script requires root privileges and you should never run it on your own machine +test $EUID -eq 0 + +PYTHON_VERSION=$(/usr/bin/python3 -c 'import sys; print("{}.{}".format(*sys.version_info))') +RPM_SITELIB="/usr/lib/python${PYTHON_VERSION}/site-packages" +LOCAL_SITELIB="/usr/local/lib/python${PYTHON_VERSION}/site-packages" +USER_SITELIB="/home/fedora-test-user/.local/lib/python${PYTHON_VERSION}/site-packages" + +# First, let's install older Pello with pip as if it was installed by RPM +# This is an approximation, but it usually works +RPM_BUILD_ROOT=/ /usr/bin/pip install 'Pello==1.0.1' + +# Now, we'll upgrade it with regular pip +/usr/bin/pip install --upgrade 'Pello==1.0.2' + +# pip should see it +/usr/bin/pip freeze | grep '^Pello==1\.0\.2$' + +# Both installations should still exist +test -d "${RPM_SITELIB}/pello-1.0.1.dist-info" +test -d "${LOCAL_SITELIB}/Pello-1.0.2.dist-info" + +# Let's ditch the local one +/usr/bin/pip uninstall --yes Pello + +# It should only remove one of them +test -d "${RPM_SITELIB}/pello-1.0.1.dist-info" +! test -d "${LOCAL_SITELIB}/Pello-1.0.2.dist-info" + +# And pip should still see the RPM-installed one +/usr/bin/pip freeze | grep '^Pello==1\.0\.1$' + +# Again, but as regular user +useradd fedora-test-user +su fedora-test-user -c '/usr/bin/pip install "Pello==1.0.2"' +test -d "${USER_SITELIB}/Pello-1.0.2.dist-info" +su fedora-test-user -c '/usr/bin/pip freeze' | grep '^Pello==1\.0\.2$' +su fedora-test-user -c '/usr/bin/pip uninstall --yes Pello' +su fedora-test-user -c '/usr/bin/pip freeze' | grep '^Pello==1\.0\.1$' diff --git a/tests/tests.yml b/tests/tests.yml deleted file mode 100644 index 631a786..0000000 --- a/tests/tests.yml +++ /dev/null @@ -1,79 +0,0 @@ ---- -- hosts: localhost - roles: - - role: standard-test-basic - tags: - - classic - repositories: - - repo: "https://src.fedoraproject.org/tests/python.git" - dest: "python" - - repo: "https://src.fedoraproject.org/rpms/pyproject-rpm-macros.git" - dest: "pyproject-rpm-macros" - tests: - - smoke34: - dir: python/smoke - run: VERSION=3.4 ./venv.sh - - smoke35: - dir: python/smoke - run: VERSION=3.5 ./venv.sh - - smoke36: - dir: python/smoke - run: VERSION=3.6 ./venv.sh - - smoke37: - dir: python/smoke - run: VERSION=3.7 ./venv.sh - - smoke38: - dir: python/smoke - run: VERSION=3.8 ./venv.sh - - smoke26: - dir: python/smoke - run: VERSION=2.6 METHOD=virtualenv TOX=false ./venv.sh - - smoke27: - dir: python/smoke - run: VERSION=2.7 METHOD=virtualenv ./venv.sh - - smoke34_virtualenv: - dir: python/smoke - run: VERSION=3.4 METHOD=virtualenv ./venv.sh - - smoke35_virtualenv: - dir: python/smoke - run: VERSION=3.5 METHOD=virtualenv ./venv.sh - - smoke36_virtualenv: - dir: python/smoke - run: VERSION=3.6 METHOD=virtualenv ./venv.sh - - smoke37_virtualenv: - dir: python/smoke - run: VERSION=3.7 METHOD=virtualenv ./venv.sh - - smoke38_virtualenv: - dir: python/smoke - run: VERSION=3.8 METHOD=virtualenv ./venv.sh - - pipenv: - run: pipenv --three && pipenv install six - - pyproject_pytest: - dir: pyproject-rpm-macros/tests - run: ./mocktest.sh python-pytest - - pyproject_entrypoints: - dir: pyproject-rpm-macros/tests - run: ./mocktest.sh python-entrypoints - - pyproject_pluggy: - dir: pyproject-rpm-macros/tests - run: ./mocktest.sh python-pluggy - - pyproject_clikit: - dir: pyproject-rpm-macros/tests - run: ./mocktest.sh python-clikit - required_packages: - - gcc - - virtualenv - - python26 - - python27 - - python34 - - python35 - - python36 - - python37 - - python38 - - python2-devel - - python3-devel - - python3-tox - - pipenv - - mock - - rpmdevtools - - rpm-build diff --git a/urllib3-1.25.3.patch b/urllib3-1.25.3.patch deleted file mode 100644 index 4504180..0000000 --- a/urllib3-1.25.3.patch +++ /dev/null @@ -1,4621 +0,0 @@ -From 0d620c4a03a8b3765ec45785299244e1a494d750 Mon Sep 17 00:00:00 2001 -From: Pradyun Gedam -Date: Sat, 20 Jul 2019 09:29:46 +0530 -Subject: [PATCH] Upgrade urllib3 to 1.25.3 - ---- - news/urllib3.vendor | 1 + - src/pip/_vendor/urllib3/LICENSE.txt | 32 +- - src/pip/_vendor/urllib3/__init__.py | 3 +- - src/pip/_vendor/urllib3/connection.py | 62 ++- - src/pip/_vendor/urllib3/connectionpool.py | 41 +- - .../contrib/_securetransport/bindings.py | 14 +- - src/pip/_vendor/urllib3/contrib/pyopenssl.py | 35 +- - .../urllib3/contrib/securetransport.py | 87 +++- - src/pip/_vendor/urllib3/contrib/socks.py | 35 +- - src/pip/_vendor/urllib3/fields.py | 140 +++++- - .../urllib3/packages/rfc3986/__init__.py | 56 +++ - .../urllib3/packages/rfc3986/_mixin.py | 353 ++++++++++++++ - .../urllib3/packages/rfc3986/abnf_regexp.py | 267 +++++++++++ - .../_vendor/urllib3/packages/rfc3986/api.py | 106 +++++ - .../urllib3/packages/rfc3986/builder.py | 298 ++++++++++++ - .../urllib3/packages/rfc3986/compat.py | 54 +++ - .../urllib3/packages/rfc3986/exceptions.py | 118 +++++ - .../_vendor/urllib3/packages/rfc3986/iri.py | 147 ++++++ - .../_vendor/urllib3/packages/rfc3986/misc.py | 124 +++++ - .../urllib3/packages/rfc3986/normalizers.py | 167 +++++++ - .../urllib3/packages/rfc3986/parseresult.py | 385 +++++++++++++++ - .../_vendor/urllib3/packages/rfc3986/uri.py | 153 ++++++ - .../urllib3/packages/rfc3986/validators.py | 450 ++++++++++++++++++ - src/pip/_vendor/urllib3/poolmanager.py | 13 +- - src/pip/_vendor/urllib3/response.py | 69 ++- - src/pip/_vendor/urllib3/util/__init__.py | 2 + - src/pip/_vendor/urllib3/util/request.py | 7 + - src/pip/_vendor/urllib3/util/retry.py | 3 +- - src/pip/_vendor/urllib3/util/ssl_.py | 111 +++-- - src/pip/_vendor/urllib3/util/timeout.py | 3 +- - src/pip/_vendor/urllib3/util/url.py | 215 ++++++--- - src/pip/_vendor/vendor.txt | 2 +- - 32 files changed, 3288 insertions(+), 265 deletions(-) - create mode 100644 news/urllib3.vendor - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/__init__.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/_mixin.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/abnf_regexp.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/api.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/builder.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/compat.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/exceptions.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/iri.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/misc.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/normalizers.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/parseresult.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/uri.py - create mode 100644 src/pip/_vendor/urllib3/packages/rfc3986/validators.py - -diff --git a/news/urllib3.vendor b/news/urllib3.vendor -new file mode 100644 -index 0000000000..ff45e8a548 ---- /dev/null -+++ b/news/urllib3.vendor -@@ -0,0 +1 @@ -+Upgrade urllib3 to 1.25.3 -diff --git a/src/pip/_vendor/urllib3/LICENSE.txt b/src/pip/_vendor/urllib3/LICENSE.txt -index 1c3283ee5b..c89cf27b85 100644 ---- a/src/pip/_vendor/urllib3/LICENSE.txt -+++ b/src/pip/_vendor/urllib3/LICENSE.txt -@@ -1,19 +1,21 @@ --This is the MIT license: http://www.opensource.org/licenses/mit-license.php -+MIT License - --Copyright 2008-2016 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -+Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt) - --Permission is hereby granted, free of charge, to any person obtaining a copy of this --software and associated documentation files (the "Software"), to deal in the Software --without restriction, including without limitation the rights to use, copy, modify, merge, --publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons --to whom the Software is furnished to do so, subject to the following conditions: -+Permission is hereby granted, free of charge, to any person obtaining a copy -+of this software and associated documentation files (the "Software"), to deal -+in the Software without restriction, including without limitation the rights -+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -+copies of the Software, and to permit persons to whom the Software is -+furnished to do so, subject to the following conditions: - --The above copyright notice and this permission notice shall be included in all copies or --substantial portions of the Software. -+The above copyright notice and this permission notice shall be included in all -+copies or substantial portions of the Software. - --THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, --INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR --PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE --FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR --OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER --DEALINGS IN THE SOFTWARE. -+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -+SOFTWARE. -diff --git a/src/pip/_vendor/urllib3/__init__.py b/src/pip/_vendor/urllib3/__init__.py -index 148a9c31a7..c4c0dde54a 100644 ---- a/src/pip/_vendor/urllib3/__init__.py -+++ b/src/pip/_vendor/urllib3/__init__.py -@@ -1,7 +1,6 @@ - """ - urllib3 - Thread-safe connection pooling and re-using. - """ -- - from __future__ import absolute_import - import warnings - -@@ -27,7 +26,7 @@ - - __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' - __license__ = 'MIT' --__version__ = '1.24.1' -+__version__ = '1.25.3' - - __all__ = ( - 'HTTPConnectionPool', -diff --git a/src/pip/_vendor/urllib3/connection.py b/src/pip/_vendor/urllib3/connection.py -index 02b36654bd..57c58fedb7 100644 ---- a/src/pip/_vendor/urllib3/connection.py -+++ b/src/pip/_vendor/urllib3/connection.py -@@ -19,10 +19,11 @@ class BaseSSLError(BaseException): - pass - - --try: # Python 3: -- # Not a no-op, we're adding this to the namespace so it can be imported. -+try: -+ # Python 3: not a no-op, we're adding this to the namespace so it can be imported. - ConnectionError = ConnectionError --except NameError: # Python 2: -+except NameError: -+ # Python 2 - class ConnectionError(Exception): - pass - -@@ -101,7 +102,7 @@ class HTTPConnection(_HTTPConnection, object): - is_verified = False - - def __init__(self, *args, **kw): -- if six.PY3: # Python 3 -+ if six.PY3: - kw.pop('strict', None) - - # Pre-set source_address. -@@ -158,7 +159,7 @@ def _new_conn(self): - conn = connection.create_connection( - (self._dns_host, self.port), self.timeout, **extra_kw) - -- except SocketTimeout as e: -+ except SocketTimeout: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) -@@ -171,7 +172,8 @@ def _new_conn(self): - - def _prepare_conn(self, conn): - self.sock = conn -- if self._tunnel_host: -+ # Google App Engine's httplib does not define _tunnel_host -+ if getattr(self, '_tunnel_host', None): - # TODO: Fix tunnel so it doesn't depend on self.sock state. - self._tunnel() - # Mark this connection as not reusable -@@ -226,7 +228,8 @@ class HTTPSConnection(HTTPConnection): - ssl_version = None - - def __init__(self, host, port=None, key_file=None, cert_file=None, -- strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, -+ key_password=None, strict=None, -+ timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - ssl_context=None, server_hostname=None, **kw): - - HTTPConnection.__init__(self, host, port, strict=strict, -@@ -234,6 +237,7 @@ def __init__(self, host, port=None, key_file=None, cert_file=None, - - self.key_file = key_file - self.cert_file = cert_file -+ self.key_password = key_password - self.ssl_context = ssl_context - self.server_hostname = server_hostname - -@@ -245,16 +249,28 @@ def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - -+ # Wrap socket using verification with the root certs in -+ # trusted_root_certs -+ default_ssl_context = False - if self.ssl_context is None: -+ default_ssl_context = True - self.ssl_context = create_urllib3_context( -- ssl_version=resolve_ssl_version(None), -- cert_reqs=resolve_cert_reqs(None), -+ ssl_version=resolve_ssl_version(self.ssl_version), -+ cert_reqs=resolve_cert_reqs(self.cert_reqs), - ) - -+ # Try to load OS default certs if none are given. -+ # Works well on Windows (requires Python3.4+) -+ context = self.ssl_context -+ if (not self.ca_certs and not self.ca_cert_dir and default_ssl_context -+ and hasattr(context, 'load_default_certs')): -+ context.load_default_certs() -+ - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, -+ key_password=self.key_password, - ssl_context=self.ssl_context, - server_hostname=self.server_hostname - ) -@@ -272,25 +288,24 @@ class VerifiedHTTPSConnection(HTTPSConnection): - assert_fingerprint = None - - def set_cert(self, key_file=None, cert_file=None, -- cert_reqs=None, ca_certs=None, -+ cert_reqs=None, key_password=None, ca_certs=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None): - """ - This method should only be called once, before the connection is used. - """ -- # If cert_reqs is not provided, we can try to guess. If the user gave -- # us a cert database, we assume they want to use it: otherwise, if -- # they gave us an SSL Context object we should use whatever is set for -- # it. -+ # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also -+ # have an SSLContext object in which case we'll use its verify_mode. - if cert_reqs is None: -- if ca_certs or ca_cert_dir: -- cert_reqs = 'CERT_REQUIRED' -- elif self.ssl_context is not None: -+ if self.ssl_context is not None: - cert_reqs = self.ssl_context.verify_mode -+ else: -+ cert_reqs = resolve_cert_reqs(None) - - self.key_file = key_file - self.cert_file = cert_file - self.cert_reqs = cert_reqs -+ self.key_password = key_password - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) -@@ -301,7 +316,8 @@ def connect(self): - conn = self._new_conn() - hostname = self.host - -- if self._tunnel_host: -+ # Google App Engine's httplib does not define _tunnel_host -+ if getattr(self, '_tunnel_host', None): - self.sock = conn - # Calls self._set_hostport(), so self.host is - # self._tunnel_host below. -@@ -326,7 +342,9 @@ def connect(self): - - # Wrap socket using verification with the root certs in - # trusted_root_certs -+ default_ssl_context = False - if self.ssl_context is None: -+ default_ssl_context = True - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(self.ssl_version), - cert_reqs=resolve_cert_reqs(self.cert_reqs), -@@ -334,10 +352,18 @@ def connect(self): - - context = self.ssl_context - context.verify_mode = resolve_cert_reqs(self.cert_reqs) -+ -+ # Try to load OS default certs if none are given. -+ # Works well on Windows (requires Python3.4+) -+ if (not self.ca_certs and not self.ca_cert_dir and default_ssl_context -+ and hasattr(context, 'load_default_certs')): -+ context.load_default_certs() -+ - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, -+ key_password=self.key_password, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - server_hostname=server_hostname, -diff --git a/src/pip/_vendor/urllib3/connectionpool.py b/src/pip/_vendor/urllib3/connectionpool.py -index f7a8f193d1..157568a395 100644 ---- a/src/pip/_vendor/urllib3/connectionpool.py -+++ b/src/pip/_vendor/urllib3/connectionpool.py -@@ -26,6 +26,7 @@ - from .packages.ssl_match_hostname import CertificateError - from .packages import six - from .packages.six.moves import queue -+from .packages.rfc3986.normalizers import normalize_host - from .connection import ( - port_by_scheme, - DummyConnection, -@@ -65,7 +66,7 @@ def __init__(self, host, port=None): - if not host: - raise LocationValueError("No host specified.") - -- self.host = _ipv6_host(host, self.scheme) -+ self.host = _normalize_host(host, scheme=self.scheme) - self._proxy_host = host.lower() - self.port = port - -@@ -373,9 +374,11 @@ def _make_request(self, conn, method, url, timeout=_Default, chunked=False, - - # Receive the response from the server - try: -- try: # Python 2.7, use buffering of HTTP responses -+ try: -+ # Python 2.7, use buffering of HTTP responses - httplib_response = conn.getresponse(buffering=True) -- except TypeError: # Python 3 -+ except TypeError: -+ # Python 3 - try: - httplib_response = conn.getresponse() - except Exception as e: -@@ -432,8 +435,8 @@ def is_same_host(self, url): - - # TODO: Add optional support for socket.gethostbyname checking. - scheme, host, port = get_host(url) -- -- host = _ipv6_host(host, self.scheme) -+ if host is not None: -+ host = _normalize_host(host, scheme=scheme) - - # Use explicit default port for comparison when none is given - if self.port and not port: -@@ -672,7 +675,7 @@ def drain_and_release_conn(response): - # released back to the pool once the entire response is read - response.read() - except (TimeoutError, HTTPException, SocketError, ProtocolError, -- BaseSSLError, SSLError) as e: -+ BaseSSLError, SSLError): - pass - - # Handle redirect? -@@ -746,8 +749,8 @@ class HTTPSConnectionPool(HTTPConnectionPool): - If ``assert_hostname`` is False, no verification is done. - - The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, -- ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is -- available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade -+ ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` -+ is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade - the connection socket into an SSL socket. - """ - -@@ -759,7 +762,7 @@ def __init__(self, host, port=None, - block=False, headers=None, retries=None, - _proxy=None, _proxy_headers=None, - key_file=None, cert_file=None, cert_reqs=None, -- ca_certs=None, ssl_version=None, -+ key_password=None, ca_certs=None, ssl_version=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None, **conn_kw): - -@@ -767,12 +770,10 @@ def __init__(self, host, port=None, - block, headers, retries, _proxy, _proxy_headers, - **conn_kw) - -- if ca_certs and cert_reqs is None: -- cert_reqs = 'CERT_REQUIRED' -- - self.key_file = key_file - self.cert_file = cert_file - self.cert_reqs = cert_reqs -+ self.key_password = key_password - self.ca_certs = ca_certs - self.ca_cert_dir = ca_cert_dir - self.ssl_version = ssl_version -@@ -787,6 +788,7 @@ def _prepare_conn(self, conn): - - if isinstance(conn, VerifiedHTTPSConnection): - conn.set_cert(key_file=self.key_file, -+ key_password=self.key_password, - cert_file=self.cert_file, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, -@@ -824,7 +826,9 @@ def _new_conn(self): - - conn = self.ConnectionCls(host=actual_host, port=actual_port, - timeout=self.timeout.connect_timeout, -- strict=self.strict, **self.conn_kw) -+ strict=self.strict, cert_file=self.cert_file, -+ key_file=self.key_file, key_password=self.key_password, -+ **self.conn_kw) - - return self._prepare_conn(conn) - -@@ -875,9 +879,9 @@ def connection_from_url(url, **kw): - return HTTPConnectionPool(host, port=port, **kw) - - --def _ipv6_host(host, scheme): -+def _normalize_host(host, scheme): - """ -- Process IPv6 address literals -+ Normalize hosts for comparisons and use with sockets. - """ - - # httplib doesn't like it when we include brackets in IPv6 addresses -@@ -886,11 +890,8 @@ def _ipv6_host(host, scheme): - # Instead, we need to make sure we never pass ``None`` as the port. - # However, for backward compatibility reasons we can't actually - # *assert* that. See http://bugs.python.org/issue28539 -- # -- # Also if an IPv6 address literal has a zone identifier, the -- # percent sign might be URIencoded, convert it back into ASCII - if host.startswith('[') and host.endswith(']'): -- host = host.replace('%25', '%').strip('[]') -+ host = host.strip('[]') - if scheme in NORMALIZABLE_SCHEMES: -- host = host.lower() -+ host = normalize_host(host) - return host -diff --git a/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py b/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py -index bcf41c02b2..be34215359 100644 ---- a/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py -+++ b/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py -@@ -516,6 +516,8 @@ class SecurityConst(object): - kTLSProtocol1 = 4 - kTLSProtocol11 = 7 - kTLSProtocol12 = 8 -+ kTLSProtocol13 = 10 -+ kTLSProtocolMaxSupported = 999 - - kSSLClientSide = 1 - kSSLStreamType = 0 -@@ -558,30 +560,27 @@ class SecurityConst(object): - errSecInvalidTrustSettings = -25262 - - # Cipher suites. We only pick the ones our default cipher string allows. -+ # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F -- TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3 -+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 -+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 - TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F -- TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2 - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 - TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B -- TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A - TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 -- TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 - TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 -- TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040 - TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 -- TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 - TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D - TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C - TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D -@@ -590,4 +589,5 @@ class SecurityConst(object): - TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F - TLS_AES_128_GCM_SHA256 = 0x1301 - TLS_AES_256_GCM_SHA384 = 0x1302 -- TLS_CHACHA20_POLY1305_SHA256 = 0x1303 -+ TLS_AES_128_CCM_8_SHA256 = 0x1305 -+ TLS_AES_128_CCM_SHA256 = 0x1304 -diff --git a/src/pip/_vendor/urllib3/contrib/pyopenssl.py b/src/pip/_vendor/urllib3/contrib/pyopenssl.py -index 363667cb56..abfc319132 100644 ---- a/src/pip/_vendor/urllib3/contrib/pyopenssl.py -+++ b/src/pip/_vendor/urllib3/contrib/pyopenssl.py -@@ -70,6 +70,7 @@ class UnsupportedExtension(Exception): - - from .. import util - -+ - __all__ = ['inject_into_urllib3', 'extract_from_urllib3'] - - # SNI always works. -@@ -77,20 +78,19 @@ class UnsupportedExtension(Exception): - - # Map from urllib3 to PyOpenSSL compatible parameter-values. - _openssl_versions = { -- ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, -+ util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, - ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, - } - -+if hasattr(ssl, 'PROTOCOL_SSLv3') and hasattr(OpenSSL.SSL, 'SSLv3_METHOD'): -+ _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD -+ - if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): - _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD - - if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): - _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD - --try: -- _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) --except AttributeError: -- pass - - _stdlib_to_openssl_verify = { - ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, -@@ -117,6 +117,7 @@ def inject_into_urllib3(): - - _validate_dependencies_met() - -+ util.SSLContext = PyOpenSSLContext - util.ssl_.SSLContext = PyOpenSSLContext - util.HAS_SNI = HAS_SNI - util.ssl_.HAS_SNI = HAS_SNI -@@ -127,6 +128,7 @@ def inject_into_urllib3(): - def extract_from_urllib3(): - 'Undo monkey-patching by :func:`inject_into_urllib3`.' - -+ util.SSLContext = orig_util_SSLContext - util.ssl_.SSLContext = orig_util_SSLContext - util.HAS_SNI = orig_util_HAS_SNI - util.ssl_.HAS_SNI = orig_util_HAS_SNI -@@ -184,6 +186,10 @@ def idna_encode(name): - except idna.core.IDNAError: - return None - -+ # Don't send IPv6 addresses through the IDNA encoder. -+ if ':' in name: -+ return name -+ - name = idna_encode(name) - if name is None: - return None -@@ -276,7 +282,7 @@ def recv(self, *args, **kwargs): - return b'' - else: - raise SocketError(str(e)) -- except OpenSSL.SSL.ZeroReturnError as e: -+ except OpenSSL.SSL.ZeroReturnError: - if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return b'' - else: -@@ -286,6 +292,10 @@ def recv(self, *args, **kwargs): - raise timeout('The read operation timed out') - else: - return self.recv(*args, **kwargs) -+ -+ # TLS 1.3 post-handshake authentication -+ except OpenSSL.SSL.Error as e: -+ raise ssl.SSLError("read error: %r" % e) - else: - return data - -@@ -297,7 +307,7 @@ def recv_into(self, *args, **kwargs): - return 0 - else: - raise SocketError(str(e)) -- except OpenSSL.SSL.ZeroReturnError as e: -+ except OpenSSL.SSL.ZeroReturnError: - if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return 0 - else: -@@ -308,6 +318,10 @@ def recv_into(self, *args, **kwargs): - else: - return self.recv_into(*args, **kwargs) - -+ # TLS 1.3 post-handshake authentication -+ except OpenSSL.SSL.Error as e: -+ raise ssl.SSLError("read error: %r" % e) -+ - def settimeout(self, timeout): - return self.socket.settimeout(timeout) - -@@ -360,6 +374,9 @@ def getpeercert(self, binary_form=False): - 'subjectAltName': get_subj_alt_name(x509) - } - -+ def version(self): -+ return self.connection.get_protocol_version_name() -+ - def _reuse(self): - self._makefile_refs += 1 - -@@ -432,7 +449,9 @@ def load_verify_locations(self, cafile=None, capath=None, cadata=None): - def load_cert_chain(self, certfile, keyfile=None, password=None): - self._ctx.use_certificate_chain_file(certfile) - if password is not None: -- self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password) -+ if not isinstance(password, six.binary_type): -+ password = password.encode('utf-8') -+ self._ctx.set_passwd_cb(lambda *_: password) - self._ctx.use_privatekey_file(keyfile or certfile) - - def wrap_socket(self, sock, server_side=False, -diff --git a/src/pip/_vendor/urllib3/contrib/securetransport.py b/src/pip/_vendor/urllib3/contrib/securetransport.py -index 77cb59ed71..4dc4848416 100644 ---- a/src/pip/_vendor/urllib3/contrib/securetransport.py -+++ b/src/pip/_vendor/urllib3/contrib/securetransport.py -@@ -23,6 +23,31 @@ - urllib3.contrib.securetransport.inject_into_urllib3() - - Happy TLSing! -+ -+This code is a bastardised version of the code found in Will Bond's oscrypto -+library. An enormous debt is owed to him for blazing this trail for us. For -+that reason, this code should be considered to be covered both by urllib3's -+license and by oscrypto's: -+ -+ Copyright (c) 2015-2016 Will Bond -+ -+ Permission is hereby granted, free of charge, to any person obtaining a -+ copy of this software and associated documentation files (the "Software"), -+ to deal in the Software without restriction, including without limitation -+ the rights to use, copy, modify, merge, publish, distribute, sublicense, -+ and/or sell copies of the Software, and to permit persons to whom the -+ Software is furnished to do so, subject to the following conditions: -+ -+ The above copyright notice and this permission notice shall be included in -+ all copies or substantial portions of the Software. -+ -+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -+ DEALINGS IN THE SOFTWARE. - """ - from __future__ import absolute_import - -@@ -86,35 +111,32 @@ - # individual cipher suites. We need to do this because this is how - # SecureTransport wants them. - CIPHER_SUITES = [ -- SecurityConst.TLS_AES_256_GCM_SHA384, -- SecurityConst.TLS_CHACHA20_POLY1305_SHA256, -- SecurityConst.TLS_AES_128_GCM_SHA256, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, -- SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, -+ SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, -- SecurityConst.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, -+ SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, -+ SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, -- SecurityConst.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, -- SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, -- SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, -- SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, -- SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, -- SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, -- SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, -- SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, -+ SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, -+ SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, -+ SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, -+ SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, -+ SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, -- SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, -- SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, -+ SecurityConst.TLS_AES_256_GCM_SHA384, -+ SecurityConst.TLS_AES_128_GCM_SHA256, - SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, -+ SecurityConst.TLS_AES_128_CCM_8_SHA256, -+ SecurityConst.TLS_AES_128_CCM_SHA256, - SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, - SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, -@@ -122,9 +144,10 @@ - ] - - # Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of --# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. -+# TLSv1 and a high of TLSv1.3. For everything else, we pin to that version. -+# TLSv1 to 1.2 are supported on macOS 10.8+ and TLSv1.3 is macOS 10.13+ - _protocol_to_min_max = { -- ssl.PROTOCOL_SSLv23: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), -+ util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocolMaxSupported), - } - - if hasattr(ssl, "PROTOCOL_SSLv2"): -@@ -147,14 +170,13 @@ - _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( - SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12 - ) --if hasattr(ssl, "PROTOCOL_TLS"): -- _protocol_to_min_max[ssl.PROTOCOL_TLS] = _protocol_to_min_max[ssl.PROTOCOL_SSLv23] - - - def inject_into_urllib3(): - """ - Monkey-patch urllib3 with SecureTransport-backed SSL-support. - """ -+ util.SSLContext = SecureTransportContext - util.ssl_.SSLContext = SecureTransportContext - util.HAS_SNI = HAS_SNI - util.ssl_.HAS_SNI = HAS_SNI -@@ -166,6 +188,7 @@ def extract_from_urllib3(): - """ - Undo monkey-patching by :func:`inject_into_urllib3`. - """ -+ util.SSLContext = orig_util_SSLContext - util.ssl_.SSLContext = orig_util_SSLContext - util.HAS_SNI = orig_util_HAS_SNI - util.ssl_.HAS_SNI = orig_util_HAS_SNI -@@ -458,7 +481,14 @@ def handshake(self, - # Set the minimum and maximum TLS versions. - result = Security.SSLSetProtocolVersionMin(self.context, min_version) - _assert_no_error(result) -+ -+ # TLS 1.3 isn't necessarily enabled by the OS -+ # so we have to detect when we error out and try -+ # setting TLS 1.3 if it's allowed. kTLSProtocolMaxSupported -+ # was added in macOS 10.13 along with kTLSProtocol13. - result = Security.SSLSetProtocolVersionMax(self.context, max_version) -+ if result != 0 and max_version == SecurityConst.kTLSProtocolMaxSupported: -+ result = Security.SSLSetProtocolVersionMax(self.context, SecurityConst.kTLSProtocol12) - _assert_no_error(result) - - # If there's a trust DB, we need to use it. We do that by telling -@@ -667,6 +697,25 @@ def getpeercert(self, binary_form=False): - - return der_bytes - -+ def version(self): -+ protocol = Security.SSLProtocol() -+ result = Security.SSLGetNegotiatedProtocolVersion(self.context, ctypes.byref(protocol)) -+ _assert_no_error(result) -+ if protocol.value == SecurityConst.kTLSProtocol13: -+ return 'TLSv1.3' -+ elif protocol.value == SecurityConst.kTLSProtocol12: -+ return 'TLSv1.2' -+ elif protocol.value == SecurityConst.kTLSProtocol11: -+ return 'TLSv1.1' -+ elif protocol.value == SecurityConst.kTLSProtocol1: -+ return 'TLSv1' -+ elif protocol.value == SecurityConst.kSSLProtocol3: -+ return 'SSLv3' -+ elif protocol.value == SecurityConst.kSSLProtocol2: -+ return 'SSLv2' -+ else: -+ raise ssl.SSLError('Unknown TLS version: %r' % protocol) -+ - def _reuse(self): - self._makefile_refs += 1 - -diff --git a/src/pip/_vendor/urllib3/contrib/socks.py b/src/pip/_vendor/urllib3/contrib/socks.py -index 811e312ec8..636d261fb0 100644 ---- a/src/pip/_vendor/urllib3/contrib/socks.py -+++ b/src/pip/_vendor/urllib3/contrib/socks.py -@@ -1,25 +1,38 @@ - # -*- coding: utf-8 -*- - """ - This module contains provisional support for SOCKS proxies from within --urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and -+urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and - SOCKS5. To enable its functionality, either install PySocks or install this - module with the ``socks`` extra. - - The SOCKS implementation supports the full range of urllib3 features. It also - supports the following SOCKS features: - --- SOCKS4 --- SOCKS4a --- SOCKS5 -+- SOCKS4A (``proxy_url='socks4a://...``) -+- SOCKS4 (``proxy_url='socks4://...``) -+- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) -+- SOCKS5 with local DNS (``proxy_url='socks5://...``) - - Usernames and passwords for the SOCKS proxy - --Known Limitations: -+ .. note:: -+ It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in -+ your ``proxy_url`` to ensure that DNS resolution is done from the remote -+ server instead of client-side when connecting to a domain name. -+ -+SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 -+supports IPv4, IPv6, and domain names. -+ -+When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` -+will be sent as the ``userid`` section of the SOCKS request:: -+ -+ proxy_url="socks4a://@proxy-host" -+ -+When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion -+of the ``proxy_url`` will be sent as the username/password to authenticate -+with the proxy:: -+ -+ proxy_url="socks5h://:@proxy-host" - --- Currently PySocks does not support contacting remote websites via literal -- IPv6 addresses. Any such connection attempt will fail. You must use a domain -- name. --- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any -- such connection attempt will fail. - """ - from __future__ import absolute_import - -@@ -88,7 +101,7 @@ def _new_conn(self): - **extra_kw - ) - -- except SocketTimeout as e: -+ except SocketTimeout: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) -diff --git a/src/pip/_vendor/urllib3/fields.py b/src/pip/_vendor/urllib3/fields.py -index 37fe64a3e8..6a9a5a7f56 100644 ---- a/src/pip/_vendor/urllib3/fields.py -+++ b/src/pip/_vendor/urllib3/fields.py -@@ -1,6 +1,7 @@ - from __future__ import absolute_import - import email.utils - import mimetypes -+import re - - from .packages import six - -@@ -19,57 +20,147 @@ def guess_content_type(filename, default='application/octet-stream'): - return default - - --def format_header_param(name, value): -+def format_header_param_rfc2231(name, value): - """ -- Helper function to format and quote a single header parameter. -+ Helper function to format and quote a single header parameter using the -+ strategy defined in RFC 2231. - - Particularly useful for header parameters which might contain -- non-ASCII values, like file names. This follows RFC 2231, as -- suggested by RFC 2388 Section 4.4. -+ non-ASCII values, like file names. This follows RFC 2388 Section 4.4. - - :param name: - The name of the parameter, a string expected to be ASCII only. - :param value: -- The value of the parameter, provided as a unicode string. -+ The value of the parameter, provided as ``bytes`` or `str``. -+ :ret: -+ An RFC-2231-formatted unicode string. - """ -+ if isinstance(value, six.binary_type): -+ value = value.decode("utf-8") -+ - if not any(ch in value for ch in '"\\\r\n'): -- result = '%s="%s"' % (name, value) -+ result = u'%s="%s"' % (name, value) - try: - result.encode('ascii') - except (UnicodeEncodeError, UnicodeDecodeError): - pass - else: - return result -- if not six.PY3 and isinstance(value, six.text_type): # Python 2: -+ -+ if not six.PY3: # Python 2: - value = value.encode('utf-8') -+ -+ # encode_rfc2231 accepts an encoded string and returns an ascii-encoded -+ # string in Python 2 but accepts and returns unicode strings in Python 3 - value = email.utils.encode_rfc2231(value, 'utf-8') - value = '%s*=%s' % (name, value) -+ -+ if not six.PY3: # Python 2: -+ value = value.decode('utf-8') -+ - return value - - -+_HTML5_REPLACEMENTS = { -+ u"\u0022": u"%22", -+ # Replace "\" with "\\". -+ u"\u005C": u"\u005C\u005C", -+ u"\u005C": u"\u005C\u005C", -+} -+ -+# All control characters from 0x00 to 0x1F *except* 0x1B. -+_HTML5_REPLACEMENTS.update({ -+ six.unichr(cc): u"%{:02X}".format(cc) -+ for cc -+ in range(0x00, 0x1F+1) -+ if cc not in (0x1B,) -+}) -+ -+ -+def _replace_multiple(value, needles_and_replacements): -+ -+ def replacer(match): -+ return needles_and_replacements[match.group(0)] -+ -+ pattern = re.compile( -+ r"|".join([ -+ re.escape(needle) for needle in needles_and_replacements.keys() -+ ]) -+ ) -+ -+ result = pattern.sub(replacer, value) -+ -+ return result -+ -+ -+def format_header_param_html5(name, value): -+ """ -+ Helper function to format and quote a single header parameter using the -+ HTML5 strategy. -+ -+ Particularly useful for header parameters which might contain -+ non-ASCII values, like file names. This follows the `HTML5 Working Draft -+ Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. -+ -+ .. _HTML5 Working Draft Section 4.10.22.7: -+ https://w3c.github.io/html/sec-forms.html#multipart-form-data -+ -+ :param name: -+ The name of the parameter, a string expected to be ASCII only. -+ :param value: -+ The value of the parameter, provided as ``bytes`` or `str``. -+ :ret: -+ A unicode string, stripped of troublesome characters. -+ """ -+ if isinstance(value, six.binary_type): -+ value = value.decode("utf-8") -+ -+ value = _replace_multiple(value, _HTML5_REPLACEMENTS) -+ -+ return u'%s="%s"' % (name, value) -+ -+ -+# For backwards-compatibility. -+format_header_param = format_header_param_html5 -+ -+ - class RequestField(object): - """ - A data container for request body parameters. - - :param name: -- The name of this request field. -+ The name of this request field. Must be unicode. - :param data: - The data/value body. - :param filename: -- An optional filename of the request field. -+ An optional filename of the request field. Must be unicode. - :param headers: - An optional dict-like object of headers to initially use for the field. -+ :param header_formatter: -+ An optional callable that is used to encode and format the headers. By -+ default, this is :func:`format_header_param_html5`. - """ -- def __init__(self, name, data, filename=None, headers=None): -+ def __init__( -+ self, -+ name, -+ data, -+ filename=None, -+ headers=None, -+ header_formatter=format_header_param_html5): - self._name = name - self._filename = filename - self.data = data - self.headers = {} - if headers: - self.headers = dict(headers) -+ self.header_formatter = header_formatter - - @classmethod -- def from_tuples(cls, fieldname, value): -+ def from_tuples( -+ cls, -+ fieldname, -+ value, -+ header_formatter=format_header_param_html5): - """ - A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. - -@@ -97,21 +188,24 @@ def from_tuples(cls, fieldname, value): - content_type = None - data = value - -- request_param = cls(fieldname, data, filename=filename) -+ request_param = cls( -+ fieldname, data, filename=filename, header_formatter=header_formatter) - request_param.make_multipart(content_type=content_type) - - return request_param - - def _render_part(self, name, value): - """ -- Overridable helper function to format a single header parameter. -+ Overridable helper function to format a single header parameter. By -+ default, this calls ``self.header_formatter``. - - :param name: - The name of the parameter, a string expected to be ASCII only. - :param value: - The value of the parameter, provided as a unicode string. - """ -- return format_header_param(name, value) -+ -+ return self.header_formatter(name, value) - - def _render_parts(self, header_parts): - """ -@@ -133,7 +227,7 @@ def _render_parts(self, header_parts): - if value is not None: - parts.append(self._render_part(name, value)) - -- return '; '.join(parts) -+ return u'; '.join(parts) - - def render_headers(self): - """ -@@ -144,15 +238,15 @@ def render_headers(self): - sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] - for sort_key in sort_keys: - if self.headers.get(sort_key, False): -- lines.append('%s: %s' % (sort_key, self.headers[sort_key])) -+ lines.append(u'%s: %s' % (sort_key, self.headers[sort_key])) - - for header_name, header_value in self.headers.items(): - if header_name not in sort_keys: - if header_value: -- lines.append('%s: %s' % (header_name, header_value)) -+ lines.append(u'%s: %s' % (header_name, header_value)) - -- lines.append('\r\n') -- return '\r\n'.join(lines) -+ lines.append(u'\r\n') -+ return u'\r\n'.join(lines) - - def make_multipart(self, content_disposition=None, content_type=None, - content_location=None): -@@ -168,10 +262,10 @@ def make_multipart(self, content_disposition=None, content_type=None, - The 'Content-Location' of the request body. - - """ -- self.headers['Content-Disposition'] = content_disposition or 'form-data' -- self.headers['Content-Disposition'] += '; '.join([ -- '', self._render_parts( -- (('name', self._name), ('filename', self._filename)) -+ self.headers['Content-Disposition'] = content_disposition or u'form-data' -+ self.headers['Content-Disposition'] += u'; '.join([ -+ u'', self._render_parts( -+ ((u'name', self._name), (u'filename', self._filename)) - ) - ]) - self.headers['Content-Type'] = content_type -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/__init__.py b/src/pip/_vendor/urllib3/packages/rfc3986/__init__.py -new file mode 100644 -index 0000000000..371c6dd519 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/__init__.py -@@ -0,0 +1,56 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+ -+""" -+An implementation of semantics and validations described in RFC 3986. -+ -+See http://rfc3986.readthedocs.io/ for detailed documentation. -+ -+:copyright: (c) 2014 Rackspace -+:license: Apache v2.0, see LICENSE for details -+""" -+ -+from .api import iri_reference -+from .api import IRIReference -+from .api import is_valid_uri -+from .api import normalize_uri -+from .api import uri_reference -+from .api import URIReference -+from .api import urlparse -+from .parseresult import ParseResult -+ -+__title__ = 'rfc3986' -+__author__ = 'Ian Stapleton Cordasco' -+__author_email__ = 'graffatcolmingov@gmail.com' -+__license__ = 'Apache v2.0' -+__copyright__ = 'Copyright 2014 Rackspace' -+__version__ = '1.3.2' -+ -+__all__ = ( -+ 'ParseResult', -+ 'URIReference', -+ 'IRIReference', -+ 'is_valid_uri', -+ 'normalize_uri', -+ 'uri_reference', -+ 'iri_reference', -+ 'urlparse', -+ '__title__', -+ '__author__', -+ '__author_email__', -+ '__license__', -+ '__copyright__', -+ '__version__', -+) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/_mixin.py b/src/pip/_vendor/urllib3/packages/rfc3986/_mixin.py -new file mode 100644 -index 0000000000..543925cdbc ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/_mixin.py -@@ -0,0 +1,353 @@ -+"""Module containing the implementation of the URIMixin class.""" -+import warnings -+ -+from . import exceptions as exc -+from . import misc -+from . import normalizers -+from . import validators -+ -+ -+class URIMixin(object): -+ """Mixin with all shared methods for URIs and IRIs.""" -+ -+ __hash__ = tuple.__hash__ -+ -+ def authority_info(self): -+ """Return a dictionary with the ``userinfo``, ``host``, and ``port``. -+ -+ If the authority is not valid, it will raise a -+ :class:`~rfc3986.exceptions.InvalidAuthority` Exception. -+ -+ :returns: -+ ``{'userinfo': 'username:password', 'host': 'www.example.com', -+ 'port': '80'}`` -+ :rtype: dict -+ :raises rfc3986.exceptions.InvalidAuthority: -+ If the authority is not ``None`` and can not be parsed. -+ """ -+ if not self.authority: -+ return {'userinfo': None, 'host': None, 'port': None} -+ -+ match = self._match_subauthority() -+ -+ if match is None: -+ # In this case, we have an authority that was parsed from the URI -+ # Reference, but it cannot be further parsed by our -+ # misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid -+ # authority. -+ raise exc.InvalidAuthority(self.authority.encode(self.encoding)) -+ -+ # We had a match, now let's ensure that it is actually a valid host -+ # address if it is IPv4 -+ matches = match.groupdict() -+ host = matches.get('host') -+ -+ if (host and misc.IPv4_MATCHER.match(host) and not -+ validators.valid_ipv4_host_address(host)): -+ # If we have a host, it appears to be IPv4 and it does not have -+ # valid bytes, it is an InvalidAuthority. -+ raise exc.InvalidAuthority(self.authority.encode(self.encoding)) -+ -+ return matches -+ -+ def _match_subauthority(self): -+ return misc.SUBAUTHORITY_MATCHER.match(self.authority) -+ -+ @property -+ def host(self): -+ """If present, a string representing the host.""" -+ try: -+ authority = self.authority_info() -+ except exc.InvalidAuthority: -+ return None -+ return authority['host'] -+ -+ @property -+ def port(self): -+ """If present, the port extracted from the authority.""" -+ try: -+ authority = self.authority_info() -+ except exc.InvalidAuthority: -+ return None -+ return authority['port'] -+ -+ @property -+ def userinfo(self): -+ """If present, the userinfo extracted from the authority.""" -+ try: -+ authority = self.authority_info() -+ except exc.InvalidAuthority: -+ return None -+ return authority['userinfo'] -+ -+ def is_absolute(self): -+ """Determine if this URI Reference is an absolute URI. -+ -+ See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation. -+ -+ :returns: ``True`` if it is an absolute URI, ``False`` otherwise. -+ :rtype: bool -+ """ -+ return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit())) -+ -+ def is_valid(self, **kwargs): -+ """Determine if the URI is valid. -+ -+ .. deprecated:: 1.1.0 -+ -+ Use the :class:`~rfc3986.validators.Validator` object instead. -+ -+ :param bool require_scheme: Set to ``True`` if you wish to require the -+ presence of the scheme component. -+ :param bool require_authority: Set to ``True`` if you wish to require -+ the presence of the authority component. -+ :param bool require_path: Set to ``True`` if you wish to require the -+ presence of the path component. -+ :param bool require_query: Set to ``True`` if you wish to require the -+ presence of the query component. -+ :param bool require_fragment: Set to ``True`` if you wish to require -+ the presence of the fragment component. -+ :returns: ``True`` if the URI is valid. ``False`` otherwise. -+ :rtype: bool -+ """ -+ warnings.warn("Please use rfc3986.validators.Validator instead. " -+ "This method will be eventually removed.", -+ DeprecationWarning) -+ validators = [ -+ (self.scheme_is_valid, kwargs.get('require_scheme', False)), -+ (self.authority_is_valid, kwargs.get('require_authority', False)), -+ (self.path_is_valid, kwargs.get('require_path', False)), -+ (self.query_is_valid, kwargs.get('require_query', False)), -+ (self.fragment_is_valid, kwargs.get('require_fragment', False)), -+ ] -+ return all(v(r) for v, r in validators) -+ -+ def authority_is_valid(self, require=False): -+ """Determine if the authority component is valid. -+ -+ .. deprecated:: 1.1.0 -+ -+ Use the :class:`~rfc3986.validators.Validator` object instead. -+ -+ :param bool require: -+ Set to ``True`` to require the presence of this component. -+ :returns: -+ ``True`` if the authority is valid. ``False`` otherwise. -+ :rtype: -+ bool -+ """ -+ warnings.warn("Please use rfc3986.validators.Validator instead. " -+ "This method will be eventually removed.", -+ DeprecationWarning) -+ try: -+ self.authority_info() -+ except exc.InvalidAuthority: -+ return False -+ -+ return validators.authority_is_valid( -+ self.authority, -+ host=self.host, -+ require=require, -+ ) -+ -+ def scheme_is_valid(self, require=False): -+ """Determine if the scheme component is valid. -+ -+ .. deprecated:: 1.1.0 -+ -+ Use the :class:`~rfc3986.validators.Validator` object instead. -+ -+ :param str require: Set to ``True`` to require the presence of this -+ component. -+ :returns: ``True`` if the scheme is valid. ``False`` otherwise. -+ :rtype: bool -+ """ -+ warnings.warn("Please use rfc3986.validators.Validator instead. " -+ "This method will be eventually removed.", -+ DeprecationWarning) -+ return validators.scheme_is_valid(self.scheme, require) -+ -+ def path_is_valid(self, require=False): -+ """Determine if the path component is valid. -+ -+ .. deprecated:: 1.1.0 -+ -+ Use the :class:`~rfc3986.validators.Validator` object instead. -+ -+ :param str require: Set to ``True`` to require the presence of this -+ component. -+ :returns: ``True`` if the path is valid. ``False`` otherwise. -+ :rtype: bool -+ """ -+ warnings.warn("Please use rfc3986.validators.Validator instead. " -+ "This method will be eventually removed.", -+ DeprecationWarning) -+ return validators.path_is_valid(self.path, require) -+ -+ def query_is_valid(self, require=False): -+ """Determine if the query component is valid. -+ -+ .. deprecated:: 1.1.0 -+ -+ Use the :class:`~rfc3986.validators.Validator` object instead. -+ -+ :param str require: Set to ``True`` to require the presence of this -+ component. -+ :returns: ``True`` if the query is valid. ``False`` otherwise. -+ :rtype: bool -+ """ -+ warnings.warn("Please use rfc3986.validators.Validator instead. " -+ "This method will be eventually removed.", -+ DeprecationWarning) -+ return validators.query_is_valid(self.query, require) -+ -+ def fragment_is_valid(self, require=False): -+ """Determine if the fragment component is valid. -+ -+ .. deprecated:: 1.1.0 -+ -+ Use the Validator object instead. -+ -+ :param str require: Set to ``True`` to require the presence of this -+ component. -+ :returns: ``True`` if the fragment is valid. ``False`` otherwise. -+ :rtype: bool -+ """ -+ warnings.warn("Please use rfc3986.validators.Validator instead. " -+ "This method will be eventually removed.", -+ DeprecationWarning) -+ return validators.fragment_is_valid(self.fragment, require) -+ -+ def normalized_equality(self, other_ref): -+ """Compare this URIReference to another URIReference. -+ -+ :param URIReference other_ref: (required), The reference with which -+ we're comparing. -+ :returns: ``True`` if the references are equal, ``False`` otherwise. -+ :rtype: bool -+ """ -+ return tuple(self.normalize()) == tuple(other_ref.normalize()) -+ -+ def resolve_with(self, base_uri, strict=False): -+ """Use an absolute URI Reference to resolve this relative reference. -+ -+ Assuming this is a relative reference that you would like to resolve, -+ use the provided base URI to resolve it. -+ -+ See http://tools.ietf.org/html/rfc3986#section-5 for more information. -+ -+ :param base_uri: Either a string or URIReference. It must be an -+ absolute URI or it will raise an exception. -+ :returns: A new URIReference which is the result of resolving this -+ reference using ``base_uri``. -+ :rtype: :class:`URIReference` -+ :raises rfc3986.exceptions.ResolutionError: -+ If the ``base_uri`` is not an absolute URI. -+ """ -+ if not isinstance(base_uri, URIMixin): -+ base_uri = type(self).from_string(base_uri) -+ -+ if not base_uri.is_absolute(): -+ raise exc.ResolutionError(base_uri) -+ -+ # This is optional per -+ # http://tools.ietf.org/html/rfc3986#section-5.2.1 -+ base_uri = base_uri.normalize() -+ -+ # The reference we're resolving -+ resolving = self -+ -+ if not strict and resolving.scheme == base_uri.scheme: -+ resolving = resolving.copy_with(scheme=None) -+ -+ # http://tools.ietf.org/html/rfc3986#page-32 -+ if resolving.scheme is not None: -+ target = resolving.copy_with( -+ path=normalizers.normalize_path(resolving.path) -+ ) -+ else: -+ if resolving.authority is not None: -+ target = resolving.copy_with( -+ scheme=base_uri.scheme, -+ path=normalizers.normalize_path(resolving.path) -+ ) -+ else: -+ if resolving.path is None: -+ if resolving.query is not None: -+ query = resolving.query -+ else: -+ query = base_uri.query -+ target = resolving.copy_with( -+ scheme=base_uri.scheme, -+ authority=base_uri.authority, -+ path=base_uri.path, -+ query=query -+ ) -+ else: -+ if resolving.path.startswith('/'): -+ path = normalizers.normalize_path(resolving.path) -+ else: -+ path = normalizers.normalize_path( -+ misc.merge_paths(base_uri, resolving.path) -+ ) -+ target = resolving.copy_with( -+ scheme=base_uri.scheme, -+ authority=base_uri.authority, -+ path=path, -+ query=resolving.query -+ ) -+ return target -+ -+ def unsplit(self): -+ """Create a URI string from the components. -+ -+ :returns: The URI Reference reconstituted as a string. -+ :rtype: str -+ """ -+ # See http://tools.ietf.org/html/rfc3986#section-5.3 -+ result_list = [] -+ if self.scheme: -+ result_list.extend([self.scheme, ':']) -+ if self.authority: -+ result_list.extend(['//', self.authority]) -+ if self.path: -+ result_list.append(self.path) -+ if self.query is not None: -+ result_list.extend(['?', self.query]) -+ if self.fragment is not None: -+ result_list.extend(['#', self.fragment]) -+ return ''.join(result_list) -+ -+ def copy_with(self, scheme=misc.UseExisting, authority=misc.UseExisting, -+ path=misc.UseExisting, query=misc.UseExisting, -+ fragment=misc.UseExisting): -+ """Create a copy of this reference with the new components. -+ -+ :param str scheme: -+ (optional) The scheme to use for the new reference. -+ :param str authority: -+ (optional) The authority to use for the new reference. -+ :param str path: -+ (optional) The path to use for the new reference. -+ :param str query: -+ (optional) The query to use for the new reference. -+ :param str fragment: -+ (optional) The fragment to use for the new reference. -+ :returns: -+ New URIReference with provided components. -+ :rtype: -+ URIReference -+ """ -+ attributes = { -+ 'scheme': scheme, -+ 'authority': authority, -+ 'path': path, -+ 'query': query, -+ 'fragment': fragment, -+ } -+ for key, value in list(attributes.items()): -+ if value is misc.UseExisting: -+ del attributes[key] -+ uri = self._replace(**attributes) -+ uri.encoding = self.encoding -+ return uri -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/abnf_regexp.py b/src/pip/_vendor/urllib3/packages/rfc3986/abnf_regexp.py -new file mode 100644 -index 0000000000..24c9c3d00a ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/abnf_regexp.py -@@ -0,0 +1,267 @@ -+# -*- coding: utf-8 -*- -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+"""Module for the regular expressions crafted from ABNF.""" -+ -+import sys -+ -+# https://tools.ietf.org/html/rfc3986#page-13 -+GEN_DELIMS = GENERIC_DELIMITERS = ":/?#[]@" -+GENERIC_DELIMITERS_SET = set(GENERIC_DELIMITERS) -+# https://tools.ietf.org/html/rfc3986#page-13 -+SUB_DELIMS = SUB_DELIMITERS = "!$&'()*+,;=" -+SUB_DELIMITERS_SET = set(SUB_DELIMITERS) -+# Escape the '*' for use in regular expressions -+SUB_DELIMITERS_RE = r"!$&'()\*+,;=" -+RESERVED_CHARS_SET = GENERIC_DELIMITERS_SET.union(SUB_DELIMITERS_SET) -+ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' -+DIGIT = '0123456789' -+# https://tools.ietf.org/html/rfc3986#section-2.3 -+UNRESERVED = UNRESERVED_CHARS = ALPHA + DIGIT + r'._!-' -+UNRESERVED_CHARS_SET = set(UNRESERVED_CHARS) -+NON_PCT_ENCODED_SET = RESERVED_CHARS_SET.union(UNRESERVED_CHARS_SET) -+# We need to escape the '-' in this case: -+UNRESERVED_RE = r'A-Za-z0-9._~\-' -+ -+# Percent encoded character values -+PERCENT_ENCODED = PCT_ENCODED = '%[A-Fa-f0-9]{2}' -+PCHAR = '([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':@]|%s)' % PCT_ENCODED -+ -+# NOTE(sigmavirus24): We're going to use more strict regular expressions -+# than appear in Appendix B for scheme. This will prevent over-eager -+# consuming of items that aren't schemes. -+SCHEME_RE = '[a-zA-Z][a-zA-Z0-9+.-]*' -+_AUTHORITY_RE = '[^/?#]*' -+_PATH_RE = '[^?#]*' -+_QUERY_RE = '[^#]*' -+_FRAGMENT_RE = '.*' -+ -+# Extracted from http://tools.ietf.org/html/rfc3986#appendix-B -+COMPONENT_PATTERN_DICT = { -+ 'scheme': SCHEME_RE, -+ 'authority': _AUTHORITY_RE, -+ 'path': _PATH_RE, -+ 'query': _QUERY_RE, -+ 'fragment': _FRAGMENT_RE, -+} -+ -+# See http://tools.ietf.org/html/rfc3986#appendix-B -+# In this case, we name each of the important matches so we can use -+# SRE_Match#groupdict to parse the values out if we so choose. This is also -+# modified to ignore other matches that are not important to the parsing of -+# the reference so we can also simply use SRE_Match#groups. -+URL_PARSING_RE = ( -+ r'(?:(?P{scheme}):)?(?://(?P{authority}))?' -+ r'(?P{path})(?:\?(?P{query}))?' -+ r'(?:#(?P{fragment}))?' -+).format(**COMPONENT_PATTERN_DICT) -+ -+ -+# ######################### -+# Authority Matcher Section -+# ######################### -+ -+# Host patterns, see: http://tools.ietf.org/html/rfc3986#section-3.2.2 -+# The pattern for a regular name, e.g., www.google.com, api.github.com -+REGULAR_NAME_RE = REG_NAME = '((?:{0}|[{1}])*)'.format( -+ '%[0-9A-Fa-f]{2}', SUB_DELIMITERS_RE + UNRESERVED_RE -+) -+# The pattern for an IPv4 address, e.g., 192.168.255.255, 127.0.0.1, -+IPv4_RE = r'([0-9]{1,3}\.){3}[0-9]{1,3}' -+# Hexadecimal characters used in each piece of an IPv6 address -+HEXDIG_RE = '[0-9A-Fa-f]{1,4}' -+# Least-significant 32 bits of an IPv6 address -+LS32_RE = '({hex}:{hex}|{ipv4})'.format(hex=HEXDIG_RE, ipv4=IPv4_RE) -+# Substitutions into the following patterns for IPv6 patterns defined -+# http://tools.ietf.org/html/rfc3986#page-20 -+_subs = {'hex': HEXDIG_RE, 'ls32': LS32_RE} -+ -+# Below: h16 = hexdig, see: https://tools.ietf.org/html/rfc5234 for details -+# about ABNF (Augmented Backus-Naur Form) use in the comments -+variations = [ -+ # 6( h16 ":" ) ls32 -+ '(%(hex)s:){6}%(ls32)s' % _subs, -+ # "::" 5( h16 ":" ) ls32 -+ '::(%(hex)s:){5}%(ls32)s' % _subs, -+ # [ h16 ] "::" 4( h16 ":" ) ls32 -+ '(%(hex)s)?::(%(hex)s:){4}%(ls32)s' % _subs, -+ # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 -+ '((%(hex)s:)?%(hex)s)?::(%(hex)s:){3}%(ls32)s' % _subs, -+ # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 -+ '((%(hex)s:){0,2}%(hex)s)?::(%(hex)s:){2}%(ls32)s' % _subs, -+ # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 -+ '((%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s' % _subs, -+ # [ *4( h16 ":" ) h16 ] "::" ls32 -+ '((%(hex)s:){0,4}%(hex)s)?::%(ls32)s' % _subs, -+ # [ *5( h16 ":" ) h16 ] "::" h16 -+ '((%(hex)s:){0,5}%(hex)s)?::%(hex)s' % _subs, -+ # [ *6( h16 ":" ) h16 ] "::" -+ '((%(hex)s:){0,6}%(hex)s)?::' % _subs, -+] -+ -+IPv6_RE = '(({0})|({1})|({2})|({3})|({4})|({5})|({6})|({7})|({8}))'.format( -+ *variations -+) -+ -+IPv_FUTURE_RE = r'v[0-9A-Fa-f]+\.[%s]+' % ( -+ UNRESERVED_RE + SUB_DELIMITERS_RE + ':' -+) -+ -+# RFC 6874 Zone ID ABNF -+ZONE_ID = '(?:[' + UNRESERVED_RE + ']|' + PCT_ENCODED + ')+' -+ -+IPv6_ADDRZ_RFC4007_RE = IPv6_RE + '(?:(?:%25|%)' + ZONE_ID + ')?' -+IPv6_ADDRZ_RE = IPv6_RE + '(?:%25' + ZONE_ID + ')?' -+ -+IP_LITERAL_RE = r'\[({0}|{1})\]'.format( -+ IPv6_ADDRZ_RFC4007_RE, -+ IPv_FUTURE_RE, -+) -+ -+# Pattern for matching the host piece of the authority -+HOST_RE = HOST_PATTERN = '({0}|{1}|{2})'.format( -+ REG_NAME, -+ IPv4_RE, -+ IP_LITERAL_RE, -+) -+USERINFO_RE = '^([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':]|%s)+' % ( -+ PCT_ENCODED -+) -+PORT_RE = '[0-9]{1,5}' -+ -+# #################### -+# Path Matcher Section -+# #################### -+ -+# See http://tools.ietf.org/html/rfc3986#section-3.3 for more information -+# about the path patterns defined below. -+segments = { -+ 'segment': PCHAR + '*', -+ # Non-zero length segment -+ 'segment-nz': PCHAR + '+', -+ # Non-zero length segment without ":" -+ 'segment-nz-nc': PCHAR.replace(':', '') + '+' -+} -+ -+# Path types taken from Section 3.3 (linked above) -+PATH_EMPTY = '^$' -+PATH_ROOTLESS = '%(segment-nz)s(/%(segment)s)*' % segments -+PATH_NOSCHEME = '%(segment-nz-nc)s(/%(segment)s)*' % segments -+PATH_ABSOLUTE = '/(%s)?' % PATH_ROOTLESS -+PATH_ABEMPTY = '(/%(segment)s)*' % segments -+PATH_RE = '^(%s|%s|%s|%s|%s)$' % ( -+ PATH_ABEMPTY, PATH_ABSOLUTE, PATH_NOSCHEME, PATH_ROOTLESS, PATH_EMPTY -+) -+ -+FRAGMENT_RE = QUERY_RE = ( -+ '^([/?:@' + UNRESERVED_RE + SUB_DELIMITERS_RE + ']|%s)*$' % PCT_ENCODED -+) -+ -+# ########################## -+# Relative reference matcher -+# ########################## -+ -+# See http://tools.ietf.org/html/rfc3986#section-4.2 for details -+RELATIVE_PART_RE = '(//%s%s|%s|%s|%s)' % ( -+ COMPONENT_PATTERN_DICT['authority'], -+ PATH_ABEMPTY, -+ PATH_ABSOLUTE, -+ PATH_NOSCHEME, -+ PATH_EMPTY, -+) -+ -+# See http://tools.ietf.org/html/rfc3986#section-3 for definition -+HIER_PART_RE = '(//%s%s|%s|%s|%s)' % ( -+ COMPONENT_PATTERN_DICT['authority'], -+ PATH_ABEMPTY, -+ PATH_ABSOLUTE, -+ PATH_ROOTLESS, -+ PATH_EMPTY, -+) -+ -+# ############### -+# IRIs / RFC 3987 -+# ############### -+ -+# Only wide-unicode gets the high-ranges of UCSCHAR -+if sys.maxunicode > 0xFFFF: # pragma: no cover -+ IPRIVATE = u'\uE000-\uF8FF\U000F0000-\U000FFFFD\U00100000-\U0010FFFD' -+ UCSCHAR_RE = ( -+ u'\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF' -+ u'\U00010000-\U0001FFFD\U00020000-\U0002FFFD' -+ u'\U00030000-\U0003FFFD\U00040000-\U0004FFFD' -+ u'\U00050000-\U0005FFFD\U00060000-\U0006FFFD' -+ u'\U00070000-\U0007FFFD\U00080000-\U0008FFFD' -+ u'\U00090000-\U0009FFFD\U000A0000-\U000AFFFD' -+ u'\U000B0000-\U000BFFFD\U000C0000-\U000CFFFD' -+ u'\U000D0000-\U000DFFFD\U000E1000-\U000EFFFD' -+ ) -+else: # pragma: no cover -+ IPRIVATE = u'\uE000-\uF8FF' -+ UCSCHAR_RE = ( -+ u'\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF' -+ ) -+ -+IUNRESERVED_RE = u'A-Za-z0-9\\._~\\-' + UCSCHAR_RE -+IPCHAR = u'([' + IUNRESERVED_RE + SUB_DELIMITERS_RE + u':@]|%s)' % PCT_ENCODED -+ -+isegments = { -+ 'isegment': IPCHAR + u'*', -+ # Non-zero length segment -+ 'isegment-nz': IPCHAR + u'+', -+ # Non-zero length segment without ":" -+ 'isegment-nz-nc': IPCHAR.replace(':', '') + u'+' -+} -+ -+IPATH_ROOTLESS = u'%(isegment-nz)s(/%(isegment)s)*' % isegments -+IPATH_NOSCHEME = u'%(isegment-nz-nc)s(/%(isegment)s)*' % isegments -+IPATH_ABSOLUTE = u'/(?:%s)?' % IPATH_ROOTLESS -+IPATH_ABEMPTY = u'(?:/%(isegment)s)*' % isegments -+IPATH_RE = u'^(?:%s|%s|%s|%s|%s)$' % ( -+ IPATH_ABEMPTY, IPATH_ABSOLUTE, IPATH_NOSCHEME, IPATH_ROOTLESS, PATH_EMPTY -+) -+ -+IREGULAR_NAME_RE = IREG_NAME = u'(?:{0}|[{1}])*'.format( -+ u'%[0-9A-Fa-f]{2}', SUB_DELIMITERS_RE + IUNRESERVED_RE -+) -+ -+IHOST_RE = IHOST_PATTERN = u'({0}|{1}|{2})'.format( -+ IREG_NAME, -+ IPv4_RE, -+ IP_LITERAL_RE, -+) -+ -+IUSERINFO_RE = u'^(?:[' + IUNRESERVED_RE + SUB_DELIMITERS_RE + u':]|%s)+' % ( -+ PCT_ENCODED -+) -+ -+IFRAGMENT_RE = (u'^(?:[/?:@' + IUNRESERVED_RE + SUB_DELIMITERS_RE -+ + u']|%s)*$' % PCT_ENCODED) -+IQUERY_RE = (u'^(?:[/?:@' + IUNRESERVED_RE + SUB_DELIMITERS_RE -+ + IPRIVATE + u']|%s)*$' % PCT_ENCODED) -+ -+IRELATIVE_PART_RE = u'(//%s%s|%s|%s|%s)' % ( -+ COMPONENT_PATTERN_DICT['authority'], -+ IPATH_ABEMPTY, -+ IPATH_ABSOLUTE, -+ IPATH_NOSCHEME, -+ PATH_EMPTY, -+) -+ -+IHIER_PART_RE = u'(//%s%s|%s|%s|%s)' % ( -+ COMPONENT_PATTERN_DICT['authority'], -+ IPATH_ABEMPTY, -+ IPATH_ABSOLUTE, -+ IPATH_ROOTLESS, -+ PATH_EMPTY, -+) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/api.py b/src/pip/_vendor/urllib3/packages/rfc3986/api.py -new file mode 100644 -index 0000000000..ddc4a1cd28 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/api.py -@@ -0,0 +1,106 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+""" -+Module containing the simple and functional API for rfc3986. -+ -+This module defines functions and provides access to the public attributes -+and classes of rfc3986. -+""" -+ -+from .iri import IRIReference -+from .parseresult import ParseResult -+from .uri import URIReference -+ -+ -+def uri_reference(uri, encoding='utf-8'): -+ """Parse a URI string into a URIReference. -+ -+ This is a convenience function. You could achieve the same end by using -+ ``URIReference.from_string(uri)``. -+ -+ :param str uri: The URI which needs to be parsed into a reference. -+ :param str encoding: The encoding of the string provided -+ :returns: A parsed URI -+ :rtype: :class:`URIReference` -+ """ -+ return URIReference.from_string(uri, encoding) -+ -+ -+def iri_reference(iri, encoding='utf-8'): -+ """Parse a IRI string into an IRIReference. -+ -+ This is a convenience function. You could achieve the same end by using -+ ``IRIReference.from_string(iri)``. -+ -+ :param str iri: The IRI which needs to be parsed into a reference. -+ :param str encoding: The encoding of the string provided -+ :returns: A parsed IRI -+ :rtype: :class:`IRIReference` -+ """ -+ return IRIReference.from_string(iri, encoding) -+ -+ -+def is_valid_uri(uri, encoding='utf-8', **kwargs): -+ """Determine if the URI given is valid. -+ -+ This is a convenience function. You could use either -+ ``uri_reference(uri).is_valid()`` or -+ ``URIReference.from_string(uri).is_valid()`` to achieve the same result. -+ -+ :param str uri: The URI to be validated. -+ :param str encoding: The encoding of the string provided -+ :param bool require_scheme: Set to ``True`` if you wish to require the -+ presence of the scheme component. -+ :param bool require_authority: Set to ``True`` if you wish to require the -+ presence of the authority component. -+ :param bool require_path: Set to ``True`` if you wish to require the -+ presence of the path component. -+ :param bool require_query: Set to ``True`` if you wish to require the -+ presence of the query component. -+ :param bool require_fragment: Set to ``True`` if you wish to require the -+ presence of the fragment component. -+ :returns: ``True`` if the URI is valid, ``False`` otherwise. -+ :rtype: bool -+ """ -+ return URIReference.from_string(uri, encoding).is_valid(**kwargs) -+ -+ -+def normalize_uri(uri, encoding='utf-8'): -+ """Normalize the given URI. -+ -+ This is a convenience function. You could use either -+ ``uri_reference(uri).normalize().unsplit()`` or -+ ``URIReference.from_string(uri).normalize().unsplit()`` instead. -+ -+ :param str uri: The URI to be normalized. -+ :param str encoding: The encoding of the string provided -+ :returns: The normalized URI. -+ :rtype: str -+ """ -+ normalized_reference = URIReference.from_string(uri, encoding).normalize() -+ return normalized_reference.unsplit() -+ -+ -+def urlparse(uri, encoding='utf-8'): -+ """Parse a given URI and return a ParseResult. -+ -+ This is a partial replacement of the standard library's urlparse function. -+ -+ :param str uri: The URI to be parsed. -+ :param str encoding: The encoding of the string provided. -+ :returns: A parsed URI -+ :rtype: :class:`~rfc3986.parseresult.ParseResult` -+ """ -+ return ParseResult.from_string(uri, encoding, strict=False) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/builder.py b/src/pip/_vendor/urllib3/packages/rfc3986/builder.py -new file mode 100644 -index 0000000000..7934279995 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/builder.py -@@ -0,0 +1,298 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2017 Ian Stapleton Cordasco -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+"""Module containing the logic for the URIBuilder object.""" -+from . import compat -+from . import normalizers -+from . import uri -+ -+ -+class URIBuilder(object): -+ """Object to aid in building up a URI Reference from parts. -+ -+ .. note:: -+ -+ This object should be instantiated by the user, but it's recommended -+ that it is not provided with arguments. Instead, use the available -+ method to populate the fields. -+ -+ """ -+ -+ def __init__(self, scheme=None, userinfo=None, host=None, port=None, -+ path=None, query=None, fragment=None): -+ """Initialize our URI builder. -+ -+ :param str scheme: -+ (optional) -+ :param str userinfo: -+ (optional) -+ :param str host: -+ (optional) -+ :param int port: -+ (optional) -+ :param str path: -+ (optional) -+ :param str query: -+ (optional) -+ :param str fragment: -+ (optional) -+ """ -+ self.scheme = scheme -+ self.userinfo = userinfo -+ self.host = host -+ self.port = port -+ self.path = path -+ self.query = query -+ self.fragment = fragment -+ -+ def __repr__(self): -+ """Provide a convenient view of our builder object.""" -+ formatstr = ('URIBuilder(scheme={b.scheme}, userinfo={b.userinfo}, ' -+ 'host={b.host}, port={b.port}, path={b.path}, ' -+ 'query={b.query}, fragment={b.fragment})') -+ return formatstr.format(b=self) -+ -+ def add_scheme(self, scheme): -+ """Add a scheme to our builder object. -+ -+ After normalizing, this will generate a new URIBuilder instance with -+ the specified scheme and all other attributes the same. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_scheme('HTTPS') -+ URIBuilder(scheme='https', userinfo=None, host=None, port=None, -+ path=None, query=None, fragment=None) -+ -+ """ -+ scheme = normalizers.normalize_scheme(scheme) -+ return URIBuilder( -+ scheme=scheme, -+ userinfo=self.userinfo, -+ host=self.host, -+ port=self.port, -+ path=self.path, -+ query=self.query, -+ fragment=self.fragment, -+ ) -+ -+ def add_credentials(self, username, password): -+ """Add credentials as the userinfo portion of the URI. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_credentials('root', 's3crete') -+ URIBuilder(scheme=None, userinfo='root:s3crete', host=None, -+ port=None, path=None, query=None, fragment=None) -+ -+ >>> URIBuilder().add_credentials('root', None) -+ URIBuilder(scheme=None, userinfo='root', host=None, -+ port=None, path=None, query=None, fragment=None) -+ """ -+ if username is None: -+ raise ValueError('Username cannot be None') -+ userinfo = normalizers.normalize_username(username) -+ -+ if password is not None: -+ userinfo = '{}:{}'.format( -+ userinfo, -+ normalizers.normalize_password(password), -+ ) -+ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=userinfo, -+ host=self.host, -+ port=self.port, -+ path=self.path, -+ query=self.query, -+ fragment=self.fragment, -+ ) -+ -+ def add_host(self, host): -+ """Add hostname to the URI. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_host('google.com') -+ URIBuilder(scheme=None, userinfo=None, host='google.com', -+ port=None, path=None, query=None, fragment=None) -+ -+ """ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=self.userinfo, -+ host=normalizers.normalize_host(host), -+ port=self.port, -+ path=self.path, -+ query=self.query, -+ fragment=self.fragment, -+ ) -+ -+ def add_port(self, port): -+ """Add port to the URI. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_port(80) -+ URIBuilder(scheme=None, userinfo=None, host=None, port='80', -+ path=None, query=None, fragment=None) -+ -+ >>> URIBuilder().add_port(443) -+ URIBuilder(scheme=None, userinfo=None, host=None, port='443', -+ path=None, query=None, fragment=None) -+ -+ """ -+ port_int = int(port) -+ if port_int < 0: -+ raise ValueError( -+ 'ports are not allowed to be negative. You provided {}'.format( -+ port_int, -+ ) -+ ) -+ if port_int > 65535: -+ raise ValueError( -+ 'ports are not allowed to be larger than 65535. ' -+ 'You provided {}'.format( -+ port_int, -+ ) -+ ) -+ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=self.userinfo, -+ host=self.host, -+ port='{}'.format(port_int), -+ path=self.path, -+ query=self.query, -+ fragment=self.fragment, -+ ) -+ -+ def add_path(self, path): -+ """Add a path to the URI. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_path('sigmavirus24/rfc3985') -+ URIBuilder(scheme=None, userinfo=None, host=None, port=None, -+ path='/sigmavirus24/rfc3986', query=None, fragment=None) -+ -+ >>> URIBuilder().add_path('/checkout.php') -+ URIBuilder(scheme=None, userinfo=None, host=None, port=None, -+ path='/checkout.php', query=None, fragment=None) -+ -+ """ -+ if not path.startswith('/'): -+ path = '/{}'.format(path) -+ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=self.userinfo, -+ host=self.host, -+ port=self.port, -+ path=normalizers.normalize_path(path), -+ query=self.query, -+ fragment=self.fragment, -+ ) -+ -+ def add_query_from(self, query_items): -+ """Generate and add a query a dictionary or list of tuples. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_query_from({'a': 'b c'}) -+ URIBuilder(scheme=None, userinfo=None, host=None, port=None, -+ path=None, query='a=b+c', fragment=None) -+ -+ >>> URIBuilder().add_query_from([('a', 'b c')]) -+ URIBuilder(scheme=None, userinfo=None, host=None, port=None, -+ path=None, query='a=b+c', fragment=None) -+ -+ """ -+ query = normalizers.normalize_query(compat.urlencode(query_items)) -+ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=self.userinfo, -+ host=self.host, -+ port=self.port, -+ path=self.path, -+ query=query, -+ fragment=self.fragment, -+ ) -+ -+ def add_query(self, query): -+ """Add a pre-formated query string to the URI. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_query('a=b&c=d') -+ URIBuilder(scheme=None, userinfo=None, host=None, port=None, -+ path=None, query='a=b&c=d', fragment=None) -+ -+ """ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=self.userinfo, -+ host=self.host, -+ port=self.port, -+ path=self.path, -+ query=normalizers.normalize_query(query), -+ fragment=self.fragment, -+ ) -+ -+ def add_fragment(self, fragment): -+ """Add a fragment to the URI. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_fragment('section-2.6.1') -+ URIBuilder(scheme=None, userinfo=None, host=None, port=None, -+ path=None, query=None, fragment='section-2.6.1') -+ -+ """ -+ return URIBuilder( -+ scheme=self.scheme, -+ userinfo=self.userinfo, -+ host=self.host, -+ port=self.port, -+ path=self.path, -+ query=self.query, -+ fragment=normalizers.normalize_fragment(fragment), -+ ) -+ -+ def finalize(self): -+ """Create a URIReference from our builder. -+ -+ .. code-block:: python -+ -+ >>> URIBuilder().add_scheme('https').add_host('github.com' -+ ... ).add_path('sigmavirus24/rfc3986').finalize().unsplit() -+ 'https://github.com/sigmavirus24/rfc3986' -+ -+ >>> URIBuilder().add_scheme('https').add_host('github.com' -+ ... ).add_path('sigmavirus24/rfc3986').add_credentials( -+ ... 'sigmavirus24', 'not-re@l').finalize().unsplit() -+ 'https://sigmavirus24:not-re%40l@github.com/sigmavirus24/rfc3986' -+ -+ """ -+ return uri.URIReference( -+ self.scheme, -+ normalizers.normalize_authority( -+ (self.userinfo, self.host, self.port) -+ ), -+ self.path, -+ self.query, -+ self.fragment, -+ ) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/compat.py b/src/pip/_vendor/urllib3/packages/rfc3986/compat.py -new file mode 100644 -index 0000000000..8968c38437 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/compat.py -@@ -0,0 +1,54 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+"""Compatibility module for Python 2 and 3 support.""" -+import sys -+ -+try: -+ from urllib.parse import quote as urlquote -+except ImportError: # Python 2.x -+ from urllib import quote as urlquote -+ -+try: -+ from urllib.parse import urlencode -+except ImportError: # Python 2.x -+ from urllib import urlencode -+ -+__all__ = ( -+ 'to_bytes', -+ 'to_str', -+ 'urlquote', -+ 'urlencode', -+) -+ -+PY3 = (3, 0) <= sys.version_info < (4, 0) -+PY2 = (2, 6) <= sys.version_info < (2, 8) -+ -+ -+if PY3: -+ unicode = str # Python 3.x -+ -+ -+def to_str(b, encoding='utf-8'): -+ """Ensure that b is text in the specified encoding.""" -+ if hasattr(b, 'decode') and not isinstance(b, unicode): -+ b = b.decode(encoding) -+ return b -+ -+ -+def to_bytes(s, encoding='utf-8'): -+ """Ensure that s is converted to bytes from the encoding.""" -+ if hasattr(s, 'encode') and not isinstance(s, bytes): -+ s = s.encode(encoding) -+ return s -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/exceptions.py b/src/pip/_vendor/urllib3/packages/rfc3986/exceptions.py -new file mode 100644 -index 0000000000..da8ca7cb1f ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/exceptions.py -@@ -0,0 +1,118 @@ -+# -*- coding: utf-8 -*- -+"""Exceptions module for rfc3986.""" -+ -+from . import compat -+ -+ -+class RFC3986Exception(Exception): -+ """Base class for all rfc3986 exception classes.""" -+ -+ pass -+ -+ -+class InvalidAuthority(RFC3986Exception): -+ """Exception when the authority string is invalid.""" -+ -+ def __init__(self, authority): -+ """Initialize the exception with the invalid authority.""" -+ super(InvalidAuthority, self).__init__( -+ u"The authority ({0}) is not valid.".format( -+ compat.to_str(authority))) -+ -+ -+class InvalidPort(RFC3986Exception): -+ """Exception when the port is invalid.""" -+ -+ def __init__(self, port): -+ """Initialize the exception with the invalid port.""" -+ super(InvalidPort, self).__init__( -+ 'The port ("{0}") is not valid.'.format(port)) -+ -+ -+class ResolutionError(RFC3986Exception): -+ """Exception to indicate a failure to resolve a URI.""" -+ -+ def __init__(self, uri): -+ """Initialize the error with the failed URI.""" -+ super(ResolutionError, self).__init__( -+ "{0} is not an absolute URI.".format(uri.unsplit())) -+ -+ -+class ValidationError(RFC3986Exception): -+ """Exception raised during Validation of a URI.""" -+ -+ pass -+ -+ -+class MissingComponentError(ValidationError): -+ """Exception raised when a required component is missing.""" -+ -+ def __init__(self, uri, *component_names): -+ """Initialize the error with the missing component name.""" -+ verb = 'was' -+ if len(component_names) > 1: -+ verb = 'were' -+ -+ self.uri = uri -+ self.components = sorted(component_names) -+ components = ', '.join(self.components) -+ super(MissingComponentError, self).__init__( -+ "{} {} required but missing".format(components, verb), -+ uri, -+ self.components, -+ ) -+ -+ -+class UnpermittedComponentError(ValidationError): -+ """Exception raised when a component has an unpermitted value.""" -+ -+ def __init__(self, component_name, component_value, allowed_values): -+ """Initialize the error with the unpermitted component.""" -+ super(UnpermittedComponentError, self).__init__( -+ "{} was required to be one of {!r} but was {!r}".format( -+ component_name, list(sorted(allowed_values)), component_value, -+ ), -+ component_name, -+ component_value, -+ allowed_values, -+ ) -+ self.component_name = component_name -+ self.component_value = component_value -+ self.allowed_values = allowed_values -+ -+ -+class PasswordForbidden(ValidationError): -+ """Exception raised when a URL has a password in the userinfo section.""" -+ -+ def __init__(self, uri): -+ """Initialize the error with the URI that failed validation.""" -+ unsplit = getattr(uri, 'unsplit', lambda: uri) -+ super(PasswordForbidden, self).__init__( -+ '"{}" contained a password when validation forbade it'.format( -+ unsplit() -+ ) -+ ) -+ self.uri = uri -+ -+ -+class InvalidComponentsError(ValidationError): -+ """Exception raised when one or more components are invalid.""" -+ -+ def __init__(self, uri, *component_names): -+ """Initialize the error with the invalid component name(s).""" -+ verb = 'was' -+ if len(component_names) > 1: -+ verb = 'were' -+ -+ self.uri = uri -+ self.components = sorted(component_names) -+ components = ', '.join(self.components) -+ super(InvalidComponentsError, self).__init__( -+ "{} {} found to be invalid".format(components, verb), -+ uri, -+ self.components, -+ ) -+ -+ -+class MissingDependencyError(RFC3986Exception): -+ """Exception raised when an IRI is encoded without the 'idna' module.""" -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/iri.py b/src/pip/_vendor/urllib3/packages/rfc3986/iri.py -new file mode 100644 -index 0000000000..416cae4a71 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/iri.py -@@ -0,0 +1,147 @@ -+"""Module containing the implementation of the IRIReference class.""" -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Copyright (c) 2015 Ian Stapleton Cordasco -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+from collections import namedtuple -+ -+from . import compat -+from . import exceptions -+from . import misc -+from . import normalizers -+from . import uri -+ -+ -+try: -+ from pip._vendor import idna -+except ImportError: # pragma: no cover -+ idna = None -+ -+ -+class IRIReference(namedtuple('IRIReference', misc.URI_COMPONENTS), -+ uri.URIMixin): -+ """Immutable object representing a parsed IRI Reference. -+ -+ Can be encoded into an URIReference object via the procedure -+ specified in RFC 3987 Section 3.1 -+ -+ .. note:: -+ The IRI submodule is a new interface and may possibly change in -+ the future. Check for changes to the interface when upgrading. -+ """ -+ -+ slots = () -+ -+ def __new__(cls, scheme, authority, path, query, fragment, -+ encoding='utf-8'): -+ """Create a new IRIReference.""" -+ ref = super(IRIReference, cls).__new__( -+ cls, -+ scheme or None, -+ authority or None, -+ path or None, -+ query, -+ fragment) -+ ref.encoding = encoding -+ return ref -+ -+ def __eq__(self, other): -+ """Compare this reference to another.""" -+ other_ref = other -+ if isinstance(other, tuple): -+ other_ref = self.__class__(*other) -+ elif not isinstance(other, IRIReference): -+ try: -+ other_ref = self.__class__.from_string(other) -+ except TypeError: -+ raise TypeError( -+ 'Unable to compare {0}() to {1}()'.format( -+ type(self).__name__, type(other).__name__)) -+ -+ # See http://tools.ietf.org/html/rfc3986#section-6.2 -+ return tuple(self) == tuple(other_ref) -+ -+ def _match_subauthority(self): -+ return misc.ISUBAUTHORITY_MATCHER.match(self.authority) -+ -+ @classmethod -+ def from_string(cls, iri_string, encoding='utf-8'): -+ """Parse a IRI reference from the given unicode IRI string. -+ -+ :param str iri_string: Unicode IRI to be parsed into a reference. -+ :param str encoding: The encoding of the string provided -+ :returns: :class:`IRIReference` or subclass thereof -+ """ -+ iri_string = compat.to_str(iri_string, encoding) -+ -+ split_iri = misc.IRI_MATCHER.match(iri_string).groupdict() -+ return cls( -+ split_iri['scheme'], split_iri['authority'], -+ normalizers.encode_component(split_iri['path'], encoding), -+ normalizers.encode_component(split_iri['query'], encoding), -+ normalizers.encode_component(split_iri['fragment'], encoding), -+ encoding, -+ ) -+ -+ def encode(self, idna_encoder=None): # noqa: C901 -+ """Encode an IRIReference into a URIReference instance. -+ -+ If the ``idna`` module is installed or the ``rfc3986[idna]`` -+ extra is used then unicode characters in the IRI host -+ component will be encoded with IDNA2008. -+ -+ :param idna_encoder: -+ Function that encodes each part of the host component -+ If not given will raise an exception if the IRI -+ contains a host component. -+ :rtype: uri.URIReference -+ :returns: A URI reference -+ """ -+ authority = self.authority -+ if authority: -+ if idna_encoder is None: -+ if idna is None: # pragma: no cover -+ raise exceptions.MissingDependencyError( -+ "Could not import the 'idna' module " -+ "and the IRI hostname requires encoding" -+ ) -+ -+ def idna_encoder(name): -+ if any(ord(c) > 128 for c in name): -+ try: -+ return idna.encode(name.lower(), -+ strict=True, -+ std3_rules=True) -+ except idna.IDNAError: -+ raise exceptions.InvalidAuthority(self.authority) -+ return name -+ -+ authority = "" -+ if self.host: -+ authority = ".".join([compat.to_str(idna_encoder(part)) -+ for part in self.host.split(".")]) -+ -+ if self.userinfo is not None: -+ authority = (normalizers.encode_component( -+ self.userinfo, self.encoding) + '@' + authority) -+ -+ if self.port is not None: -+ authority += ":" + str(self.port) -+ -+ return uri.URIReference(self.scheme, -+ authority, -+ path=self.path, -+ query=self.query, -+ fragment=self.fragment, -+ encoding=self.encoding) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/misc.py b/src/pip/_vendor/urllib3/packages/rfc3986/misc.py -new file mode 100644 -index 0000000000..b735e04402 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/misc.py -@@ -0,0 +1,124 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+""" -+Module containing compiled regular expressions and constants. -+ -+This module contains important constants, patterns, and compiled regular -+expressions for parsing and validating URIs and their components. -+""" -+ -+import re -+ -+from . import abnf_regexp -+ -+# These are enumerated for the named tuple used as a superclass of -+# URIReference -+URI_COMPONENTS = ['scheme', 'authority', 'path', 'query', 'fragment'] -+ -+important_characters = { -+ 'generic_delimiters': abnf_regexp.GENERIC_DELIMITERS, -+ 'sub_delimiters': abnf_regexp.SUB_DELIMITERS, -+ # We need to escape the '*' in this case -+ 're_sub_delimiters': abnf_regexp.SUB_DELIMITERS_RE, -+ 'unreserved_chars': abnf_regexp.UNRESERVED_CHARS, -+ # We need to escape the '-' in this case: -+ 're_unreserved': abnf_regexp.UNRESERVED_RE, -+} -+ -+# For details about delimiters and reserved characters, see: -+# http://tools.ietf.org/html/rfc3986#section-2.2 -+GENERIC_DELIMITERS = abnf_regexp.GENERIC_DELIMITERS_SET -+SUB_DELIMITERS = abnf_regexp.SUB_DELIMITERS_SET -+RESERVED_CHARS = abnf_regexp.RESERVED_CHARS_SET -+# For details about unreserved characters, see: -+# http://tools.ietf.org/html/rfc3986#section-2.3 -+UNRESERVED_CHARS = abnf_regexp.UNRESERVED_CHARS_SET -+NON_PCT_ENCODED = abnf_regexp.NON_PCT_ENCODED_SET -+ -+URI_MATCHER = re.compile(abnf_regexp.URL_PARSING_RE) -+ -+SUBAUTHORITY_MATCHER = re.compile(( -+ '^(?:(?P{0})@)?' # userinfo -+ '(?P{1})' # host -+ ':?(?P{2})?$' # port -+ ).format(abnf_regexp.USERINFO_RE, -+ abnf_regexp.HOST_PATTERN, -+ abnf_regexp.PORT_RE)) -+ -+ -+HOST_MATCHER = re.compile('^' + abnf_regexp.HOST_RE + '$') -+IPv4_MATCHER = re.compile('^' + abnf_regexp.IPv4_RE + '$') -+IPv6_MATCHER = re.compile(r'^\[' + abnf_regexp.IPv6_ADDRZ_RFC4007_RE + r'\]$') -+ -+# Used by host validator -+IPv6_NO_RFC4007_MATCHER = re.compile(r'^\[%s\]$' % ( -+ abnf_regexp.IPv6_ADDRZ_RE -+)) -+ -+# Matcher used to validate path components -+PATH_MATCHER = re.compile(abnf_regexp.PATH_RE) -+ -+ -+# ################################## -+# Query and Fragment Matcher Section -+# ################################## -+ -+QUERY_MATCHER = re.compile(abnf_regexp.QUERY_RE) -+ -+FRAGMENT_MATCHER = QUERY_MATCHER -+ -+# Scheme validation, see: http://tools.ietf.org/html/rfc3986#section-3.1 -+SCHEME_MATCHER = re.compile('^{0}$'.format(abnf_regexp.SCHEME_RE)) -+ -+RELATIVE_REF_MATCHER = re.compile(r'^%s(\?%s)?(#%s)?$' % ( -+ abnf_regexp.RELATIVE_PART_RE, -+ abnf_regexp.QUERY_RE, -+ abnf_regexp.FRAGMENT_RE, -+)) -+ -+# See http://tools.ietf.org/html/rfc3986#section-4.3 -+ABSOLUTE_URI_MATCHER = re.compile(r'^%s:%s(\?%s)?$' % ( -+ abnf_regexp.COMPONENT_PATTERN_DICT['scheme'], -+ abnf_regexp.HIER_PART_RE, -+ abnf_regexp.QUERY_RE[1:-1], -+)) -+ -+# ############### -+# IRIs / RFC 3987 -+# ############### -+ -+IRI_MATCHER = re.compile(abnf_regexp.URL_PARSING_RE, re.UNICODE) -+ -+ISUBAUTHORITY_MATCHER = re.compile(( -+ u'^(?:(?P{0})@)?' # iuserinfo -+ u'(?P{1})' # ihost -+ u':?(?P{2})?$' # port -+ ).format(abnf_regexp.IUSERINFO_RE, -+ abnf_regexp.IHOST_RE, -+ abnf_regexp.PORT_RE), re.UNICODE) -+ -+ -+# Path merger as defined in http://tools.ietf.org/html/rfc3986#section-5.2.3 -+def merge_paths(base_uri, relative_path): -+ """Merge a base URI's path with a relative URI's path.""" -+ if base_uri.path is None and base_uri.authority is not None: -+ return '/' + relative_path -+ else: -+ path = base_uri.path or '' -+ index = path.rfind('/') -+ return path[:index] + '/' + relative_path -+ -+ -+UseExisting = object() -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/normalizers.py b/src/pip/_vendor/urllib3/packages/rfc3986/normalizers.py -new file mode 100644 -index 0000000000..2eb1bb36f7 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/normalizers.py -@@ -0,0 +1,167 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+"""Module with functions to normalize components.""" -+import re -+ -+from . import compat -+from . import misc -+ -+ -+def normalize_scheme(scheme): -+ """Normalize the scheme component.""" -+ return scheme.lower() -+ -+ -+def normalize_authority(authority): -+ """Normalize an authority tuple to a string.""" -+ userinfo, host, port = authority -+ result = '' -+ if userinfo: -+ result += normalize_percent_characters(userinfo) + '@' -+ if host: -+ result += normalize_host(host) -+ if port: -+ result += ':' + port -+ return result -+ -+ -+def normalize_username(username): -+ """Normalize a username to make it safe to include in userinfo.""" -+ return compat.urlquote(username) -+ -+ -+def normalize_password(password): -+ """Normalize a password to make safe for userinfo.""" -+ return compat.urlquote(password) -+ -+ -+def normalize_host(host): -+ """Normalize a host string.""" -+ if misc.IPv6_MATCHER.match(host): -+ percent = host.find('%') -+ if percent != -1: -+ percent_25 = host.find('%25') -+ -+ # Replace RFC 4007 IPv6 Zone ID delimiter '%' with '%25' -+ # from RFC 6874. If the host is '[%25]' then we -+ # assume RFC 4007 and normalize to '[%2525]' -+ if percent_25 == -1 or percent < percent_25 or \ -+ (percent == percent_25 and percent_25 == len(host) - 4): -+ host = host.replace('%', '%25', 1) -+ -+ # Don't normalize the casing of the Zone ID -+ return host[:percent].lower() + host[percent:] -+ -+ return host.lower() -+ -+ -+def normalize_path(path): -+ """Normalize the path string.""" -+ if not path: -+ return path -+ -+ path = normalize_percent_characters(path) -+ return remove_dot_segments(path) -+ -+ -+def normalize_query(query): -+ """Normalize the query string.""" -+ if not query: -+ return query -+ return normalize_percent_characters(query) -+ -+ -+def normalize_fragment(fragment): -+ """Normalize the fragment string.""" -+ if not fragment: -+ return fragment -+ return normalize_percent_characters(fragment) -+ -+ -+PERCENT_MATCHER = re.compile('%[A-Fa-f0-9]{2}') -+ -+ -+def normalize_percent_characters(s): -+ """All percent characters should be upper-cased. -+ -+ For example, ``"%3afoo%DF%ab"`` should be turned into ``"%3Afoo%DF%AB"``. -+ """ -+ matches = set(PERCENT_MATCHER.findall(s)) -+ for m in matches: -+ if not m.isupper(): -+ s = s.replace(m, m.upper()) -+ return s -+ -+ -+def remove_dot_segments(s): -+ """Remove dot segments from the string. -+ -+ See also Section 5.2.4 of :rfc:`3986`. -+ """ -+ # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code -+ segments = s.split('/') # Turn the path into a list of segments -+ output = [] # Initialize the variable to use to store output -+ -+ for segment in segments: -+ # '.' is the current directory, so ignore it, it is superfluous -+ if segment == '.': -+ continue -+ # Anything other than '..', should be appended to the output -+ elif segment != '..': -+ output.append(segment) -+ # In this case segment == '..', if we can, we should pop the last -+ # element -+ elif output: -+ output.pop() -+ -+ # If the path starts with '/' and the output is empty or the first string -+ # is non-empty -+ if s.startswith('/') and (not output or output[0]): -+ output.insert(0, '') -+ -+ # If the path starts with '/.' or '/..' ensure we add one more empty -+ # string to add a trailing '/' -+ if s.endswith(('/.', '/..')): -+ output.append('') -+ -+ return '/'.join(output) -+ -+ -+def encode_component(uri_component, encoding): -+ """Encode the specific component in the provided encoding.""" -+ if uri_component is None: -+ return uri_component -+ -+ # Try to see if the component we're encoding is already percent-encoded -+ # so we can skip all '%' characters but still encode all others. -+ percent_encodings = len(PERCENT_MATCHER.findall( -+ compat.to_str(uri_component, encoding))) -+ -+ uri_bytes = compat.to_bytes(uri_component, encoding) -+ is_percent_encoded = percent_encodings == uri_bytes.count(b'%') -+ -+ encoded_uri = bytearray() -+ -+ for i in range(0, len(uri_bytes)): -+ # Will return a single character bytestring on both Python 2 & 3 -+ byte = uri_bytes[i:i+1] -+ byte_ord = ord(byte) -+ if ((is_percent_encoded and byte == b'%') -+ or (byte_ord < 128 and byte.decode() in misc.NON_PCT_ENCODED)): -+ encoded_uri.extend(byte) -+ continue -+ encoded_uri.extend('%{0:02x}'.format(byte_ord).encode().upper()) -+ -+ return encoded_uri.decode(encoding) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/parseresult.py b/src/pip/_vendor/urllib3/packages/rfc3986/parseresult.py -new file mode 100644 -index 0000000000..0a73456693 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/parseresult.py -@@ -0,0 +1,385 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2015 Ian Stapleton Cordasco -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+"""Module containing the urlparse compatibility logic.""" -+from collections import namedtuple -+ -+from . import compat -+from . import exceptions -+from . import misc -+from . import normalizers -+from . import uri -+ -+__all__ = ('ParseResult', 'ParseResultBytes') -+ -+PARSED_COMPONENTS = ('scheme', 'userinfo', 'host', 'port', 'path', 'query', -+ 'fragment') -+ -+ -+class ParseResultMixin(object): -+ def _generate_authority(self, attributes): -+ # I swear I did not align the comparisons below. That's just how they -+ # happened to align based on pep8 and attribute lengths. -+ userinfo, host, port = (attributes[p] -+ for p in ('userinfo', 'host', 'port')) -+ if (self.userinfo != userinfo or -+ self.host != host or -+ self.port != port): -+ if port: -+ port = '{0}'.format(port) -+ return normalizers.normalize_authority( -+ (compat.to_str(userinfo, self.encoding), -+ compat.to_str(host, self.encoding), -+ port) -+ ) -+ return self.authority -+ -+ def geturl(self): -+ """Shim to match the standard library method.""" -+ return self.unsplit() -+ -+ @property -+ def hostname(self): -+ """Shim to match the standard library.""" -+ return self.host -+ -+ @property -+ def netloc(self): -+ """Shim to match the standard library.""" -+ return self.authority -+ -+ @property -+ def params(self): -+ """Shim to match the standard library.""" -+ return self.query -+ -+ -+class ParseResult(namedtuple('ParseResult', PARSED_COMPONENTS), -+ ParseResultMixin): -+ """Implementation of urlparse compatibility class. -+ -+ This uses the URIReference logic to handle compatibility with the -+ urlparse.ParseResult class. -+ """ -+ -+ slots = () -+ -+ def __new__(cls, scheme, userinfo, host, port, path, query, fragment, -+ uri_ref, encoding='utf-8'): -+ """Create a new ParseResult.""" -+ parse_result = super(ParseResult, cls).__new__( -+ cls, -+ scheme or None, -+ userinfo or None, -+ host, -+ port or None, -+ path or None, -+ query, -+ fragment) -+ parse_result.encoding = encoding -+ parse_result.reference = uri_ref -+ return parse_result -+ -+ @classmethod -+ def from_parts(cls, scheme=None, userinfo=None, host=None, port=None, -+ path=None, query=None, fragment=None, encoding='utf-8'): -+ """Create a ParseResult instance from its parts.""" -+ authority = '' -+ if userinfo is not None: -+ authority += userinfo + '@' -+ if host is not None: -+ authority += host -+ if port is not None: -+ authority += ':{0}'.format(port) -+ uri_ref = uri.URIReference(scheme=scheme, -+ authority=authority, -+ path=path, -+ query=query, -+ fragment=fragment, -+ encoding=encoding).normalize() -+ userinfo, host, port = authority_from(uri_ref, strict=True) -+ return cls(scheme=uri_ref.scheme, -+ userinfo=userinfo, -+ host=host, -+ port=port, -+ path=uri_ref.path, -+ query=uri_ref.query, -+ fragment=uri_ref.fragment, -+ uri_ref=uri_ref, -+ encoding=encoding) -+ -+ @classmethod -+ def from_string(cls, uri_string, encoding='utf-8', strict=True, -+ lazy_normalize=True): -+ """Parse a URI from the given unicode URI string. -+ -+ :param str uri_string: Unicode URI to be parsed into a reference. -+ :param str encoding: The encoding of the string provided -+ :param bool strict: Parse strictly according to :rfc:`3986` if True. -+ If False, parse similarly to the standard library's urlparse -+ function. -+ :returns: :class:`ParseResult` or subclass thereof -+ """ -+ reference = uri.URIReference.from_string(uri_string, encoding) -+ if not lazy_normalize: -+ reference = reference.normalize() -+ userinfo, host, port = authority_from(reference, strict) -+ -+ return cls(scheme=reference.scheme, -+ userinfo=userinfo, -+ host=host, -+ port=port, -+ path=reference.path, -+ query=reference.query, -+ fragment=reference.fragment, -+ uri_ref=reference, -+ encoding=encoding) -+ -+ @property -+ def authority(self): -+ """Return the normalized authority.""" -+ return self.reference.authority -+ -+ def copy_with(self, scheme=misc.UseExisting, userinfo=misc.UseExisting, -+ host=misc.UseExisting, port=misc.UseExisting, -+ path=misc.UseExisting, query=misc.UseExisting, -+ fragment=misc.UseExisting): -+ """Create a copy of this instance replacing with specified parts.""" -+ attributes = zip(PARSED_COMPONENTS, -+ (scheme, userinfo, host, port, path, query, fragment)) -+ attrs_dict = {} -+ for name, value in attributes: -+ if value is misc.UseExisting: -+ value = getattr(self, name) -+ attrs_dict[name] = value -+ authority = self._generate_authority(attrs_dict) -+ ref = self.reference.copy_with(scheme=attrs_dict['scheme'], -+ authority=authority, -+ path=attrs_dict['path'], -+ query=attrs_dict['query'], -+ fragment=attrs_dict['fragment']) -+ return ParseResult(uri_ref=ref, encoding=self.encoding, **attrs_dict) -+ -+ def encode(self, encoding=None): -+ """Convert to an instance of ParseResultBytes.""" -+ encoding = encoding or self.encoding -+ attrs = dict( -+ zip(PARSED_COMPONENTS, -+ (attr.encode(encoding) if hasattr(attr, 'encode') else attr -+ for attr in self))) -+ return ParseResultBytes( -+ uri_ref=self.reference, -+ encoding=encoding, -+ **attrs -+ ) -+ -+ def unsplit(self, use_idna=False): -+ """Create a URI string from the components. -+ -+ :returns: The parsed URI reconstituted as a string. -+ :rtype: str -+ """ -+ parse_result = self -+ if use_idna and self.host: -+ hostbytes = self.host.encode('idna') -+ host = hostbytes.decode(self.encoding) -+ parse_result = self.copy_with(host=host) -+ return parse_result.reference.unsplit() -+ -+ -+class ParseResultBytes(namedtuple('ParseResultBytes', PARSED_COMPONENTS), -+ ParseResultMixin): -+ """Compatibility shim for the urlparse.ParseResultBytes object.""" -+ -+ def __new__(cls, scheme, userinfo, host, port, path, query, fragment, -+ uri_ref, encoding='utf-8', lazy_normalize=True): -+ """Create a new ParseResultBytes instance.""" -+ parse_result = super(ParseResultBytes, cls).__new__( -+ cls, -+ scheme or None, -+ userinfo or None, -+ host, -+ port or None, -+ path or None, -+ query or None, -+ fragment or None) -+ parse_result.encoding = encoding -+ parse_result.reference = uri_ref -+ parse_result.lazy_normalize = lazy_normalize -+ return parse_result -+ -+ @classmethod -+ def from_parts(cls, scheme=None, userinfo=None, host=None, port=None, -+ path=None, query=None, fragment=None, encoding='utf-8', -+ lazy_normalize=True): -+ """Create a ParseResult instance from its parts.""" -+ authority = '' -+ if userinfo is not None: -+ authority += userinfo + '@' -+ if host is not None: -+ authority += host -+ if port is not None: -+ authority += ':{0}'.format(int(port)) -+ uri_ref = uri.URIReference(scheme=scheme, -+ authority=authority, -+ path=path, -+ query=query, -+ fragment=fragment, -+ encoding=encoding) -+ if not lazy_normalize: -+ uri_ref = uri_ref.normalize() -+ to_bytes = compat.to_bytes -+ userinfo, host, port = authority_from(uri_ref, strict=True) -+ return cls(scheme=to_bytes(scheme, encoding), -+ userinfo=to_bytes(userinfo, encoding), -+ host=to_bytes(host, encoding), -+ port=port, -+ path=to_bytes(path, encoding), -+ query=to_bytes(query, encoding), -+ fragment=to_bytes(fragment, encoding), -+ uri_ref=uri_ref, -+ encoding=encoding, -+ lazy_normalize=lazy_normalize) -+ -+ @classmethod -+ def from_string(cls, uri_string, encoding='utf-8', strict=True, -+ lazy_normalize=True): -+ """Parse a URI from the given unicode URI string. -+ -+ :param str uri_string: Unicode URI to be parsed into a reference. -+ :param str encoding: The encoding of the string provided -+ :param bool strict: Parse strictly according to :rfc:`3986` if True. -+ If False, parse similarly to the standard library's urlparse -+ function. -+ :returns: :class:`ParseResultBytes` or subclass thereof -+ """ -+ reference = uri.URIReference.from_string(uri_string, encoding) -+ if not lazy_normalize: -+ reference = reference.normalize() -+ userinfo, host, port = authority_from(reference, strict) -+ -+ to_bytes = compat.to_bytes -+ return cls(scheme=to_bytes(reference.scheme, encoding), -+ userinfo=to_bytes(userinfo, encoding), -+ host=to_bytes(host, encoding), -+ port=port, -+ path=to_bytes(reference.path, encoding), -+ query=to_bytes(reference.query, encoding), -+ fragment=to_bytes(reference.fragment, encoding), -+ uri_ref=reference, -+ encoding=encoding, -+ lazy_normalize=lazy_normalize) -+ -+ @property -+ def authority(self): -+ """Return the normalized authority.""" -+ return self.reference.authority.encode(self.encoding) -+ -+ def copy_with(self, scheme=misc.UseExisting, userinfo=misc.UseExisting, -+ host=misc.UseExisting, port=misc.UseExisting, -+ path=misc.UseExisting, query=misc.UseExisting, -+ fragment=misc.UseExisting, lazy_normalize=True): -+ """Create a copy of this instance replacing with specified parts.""" -+ attributes = zip(PARSED_COMPONENTS, -+ (scheme, userinfo, host, port, path, query, fragment)) -+ attrs_dict = {} -+ for name, value in attributes: -+ if value is misc.UseExisting: -+ value = getattr(self, name) -+ if not isinstance(value, bytes) and hasattr(value, 'encode'): -+ value = value.encode(self.encoding) -+ attrs_dict[name] = value -+ authority = self._generate_authority(attrs_dict) -+ to_str = compat.to_str -+ ref = self.reference.copy_with( -+ scheme=to_str(attrs_dict['scheme'], self.encoding), -+ authority=to_str(authority, self.encoding), -+ path=to_str(attrs_dict['path'], self.encoding), -+ query=to_str(attrs_dict['query'], self.encoding), -+ fragment=to_str(attrs_dict['fragment'], self.encoding) -+ ) -+ if not lazy_normalize: -+ ref = ref.normalize() -+ return ParseResultBytes( -+ uri_ref=ref, -+ encoding=self.encoding, -+ lazy_normalize=lazy_normalize, -+ **attrs_dict -+ ) -+ -+ def unsplit(self, use_idna=False): -+ """Create a URI bytes object from the components. -+ -+ :returns: The parsed URI reconstituted as a string. -+ :rtype: bytes -+ """ -+ parse_result = self -+ if use_idna and self.host: -+ # self.host is bytes, to encode to idna, we need to decode it -+ # first -+ host = self.host.decode(self.encoding) -+ hostbytes = host.encode('idna') -+ parse_result = self.copy_with(host=hostbytes) -+ if self.lazy_normalize: -+ parse_result = parse_result.copy_with(lazy_normalize=False) -+ uri = parse_result.reference.unsplit() -+ return uri.encode(self.encoding) -+ -+ -+def split_authority(authority): -+ # Initialize our expected return values -+ userinfo = host = port = None -+ # Initialize an extra var we may need to use -+ extra_host = None -+ # Set-up rest in case there is no userinfo portion -+ rest = authority -+ -+ if '@' in authority: -+ userinfo, rest = authority.rsplit('@', 1) -+ -+ # Handle IPv6 host addresses -+ if rest.startswith('['): -+ host, rest = rest.split(']', 1) -+ host += ']' -+ -+ if ':' in rest: -+ extra_host, port = rest.split(':', 1) -+ elif not host and rest: -+ host = rest -+ -+ if extra_host and not host: -+ host = extra_host -+ -+ return userinfo, host, port -+ -+ -+def authority_from(reference, strict): -+ try: -+ subauthority = reference.authority_info() -+ except exceptions.InvalidAuthority: -+ if strict: -+ raise -+ userinfo, host, port = split_authority(reference.authority) -+ else: -+ # Thanks to Richard Barrell for this idea: -+ # https://twitter.com/0x2ba22e11/status/617338811975139328 -+ userinfo, host, port = (subauthority.get(p) -+ for p in ('userinfo', 'host', 'port')) -+ -+ if port: -+ try: -+ port = int(port) -+ except ValueError: -+ raise exceptions.InvalidPort(port) -+ return userinfo, host, port -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/uri.py b/src/pip/_vendor/urllib3/packages/rfc3986/uri.py -new file mode 100644 -index 0000000000..d1d71505e2 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/uri.py -@@ -0,0 +1,153 @@ -+"""Module containing the implementation of the URIReference class.""" -+# -*- coding: utf-8 -*- -+# Copyright (c) 2014 Rackspace -+# Copyright (c) 2015 Ian Stapleton Cordasco -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+from collections import namedtuple -+ -+from . import compat -+from . import misc -+from . import normalizers -+from ._mixin import URIMixin -+ -+ -+class URIReference(namedtuple('URIReference', misc.URI_COMPONENTS), URIMixin): -+ """Immutable object representing a parsed URI Reference. -+ -+ .. note:: -+ -+ This class is not intended to be directly instantiated by the user. -+ -+ This object exposes attributes for the following components of a -+ URI: -+ -+ - scheme -+ - authority -+ - path -+ - query -+ - fragment -+ -+ .. attribute:: scheme -+ -+ The scheme that was parsed for the URI Reference. For example, -+ ``http``, ``https``, ``smtp``, ``imap``, etc. -+ -+ .. attribute:: authority -+ -+ Component of the URI that contains the user information, host, -+ and port sub-components. For example, -+ ``google.com``, ``127.0.0.1:5000``, ``username@[::1]``, -+ ``username:password@example.com:443``, etc. -+ -+ .. attribute:: path -+ -+ The path that was parsed for the given URI Reference. For example, -+ ``/``, ``/index.php``, etc. -+ -+ .. attribute:: query -+ -+ The query component for a given URI Reference. For example, ``a=b``, -+ ``a=b%20c``, ``a=b+c``, ``a=b,c=d,e=%20f``, etc. -+ -+ .. attribute:: fragment -+ -+ The fragment component of a URI. For example, ``section-3.1``. -+ -+ This class also provides extra attributes for easier access to information -+ like the subcomponents of the authority component. -+ -+ .. attribute:: userinfo -+ -+ The user information parsed from the authority. -+ -+ .. attribute:: host -+ -+ The hostname, IPv4, or IPv6 adddres parsed from the authority. -+ -+ .. attribute:: port -+ -+ The port parsed from the authority. -+ """ -+ -+ slots = () -+ -+ def __new__(cls, scheme, authority, path, query, fragment, -+ encoding='utf-8'): -+ """Create a new URIReference.""" -+ ref = super(URIReference, cls).__new__( -+ cls, -+ scheme or None, -+ authority or None, -+ path or None, -+ query, -+ fragment) -+ ref.encoding = encoding -+ return ref -+ -+ __hash__ = tuple.__hash__ -+ -+ def __eq__(self, other): -+ """Compare this reference to another.""" -+ other_ref = other -+ if isinstance(other, tuple): -+ other_ref = URIReference(*other) -+ elif not isinstance(other, URIReference): -+ try: -+ other_ref = URIReference.from_string(other) -+ except TypeError: -+ raise TypeError( -+ 'Unable to compare URIReference() to {0}()'.format( -+ type(other).__name__)) -+ -+ # See http://tools.ietf.org/html/rfc3986#section-6.2 -+ naive_equality = tuple(self) == tuple(other_ref) -+ return naive_equality or self.normalized_equality(other_ref) -+ -+ def normalize(self): -+ """Normalize this reference as described in Section 6.2.2. -+ -+ This is not an in-place normalization. Instead this creates a new -+ URIReference. -+ -+ :returns: A new reference object with normalized components. -+ :rtype: URIReference -+ """ -+ # See http://tools.ietf.org/html/rfc3986#section-6.2.2 for logic in -+ # this method. -+ return URIReference(normalizers.normalize_scheme(self.scheme or ''), -+ normalizers.normalize_authority( -+ (self.userinfo, self.host, self.port)), -+ normalizers.normalize_path(self.path or ''), -+ normalizers.normalize_query(self.query), -+ normalizers.normalize_fragment(self.fragment), -+ self.encoding) -+ -+ @classmethod -+ def from_string(cls, uri_string, encoding='utf-8'): -+ """Parse a URI reference from the given unicode URI string. -+ -+ :param str uri_string: Unicode URI to be parsed into a reference. -+ :param str encoding: The encoding of the string provided -+ :returns: :class:`URIReference` or subclass thereof -+ """ -+ uri_string = compat.to_str(uri_string, encoding) -+ -+ split_uri = misc.URI_MATCHER.match(uri_string).groupdict() -+ return cls( -+ split_uri['scheme'], split_uri['authority'], -+ normalizers.encode_component(split_uri['path'], encoding), -+ normalizers.encode_component(split_uri['query'], encoding), -+ normalizers.encode_component(split_uri['fragment'], encoding), -+ encoding, -+ ) -diff --git a/src/pip/_vendor/urllib3/packages/rfc3986/validators.py b/src/pip/_vendor/urllib3/packages/rfc3986/validators.py -new file mode 100644 -index 0000000000..7fc97215b1 ---- /dev/null -+++ b/src/pip/_vendor/urllib3/packages/rfc3986/validators.py -@@ -0,0 +1,450 @@ -+# -*- coding: utf-8 -*- -+# Copyright (c) 2017 Ian Stapleton Cordasco -+# Licensed under the Apache License, Version 2.0 (the "License"); -+# you may not use this file except in compliance with the License. -+# You may obtain a copy of the License at -+# -+# http://www.apache.org/licenses/LICENSE-2.0 -+# -+# Unless required by applicable law or agreed to in writing, software -+# distributed under the License is distributed on an "AS IS" BASIS, -+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -+# implied. -+# See the License for the specific language governing permissions and -+# limitations under the License. -+"""Module containing the validation logic for rfc3986.""" -+from . import exceptions -+from . import misc -+from . import normalizers -+ -+ -+class Validator(object): -+ """Object used to configure validation of all objects in rfc3986. -+ -+ .. versionadded:: 1.0 -+ -+ Example usage:: -+ -+ >>> from rfc3986 import api, validators -+ >>> uri = api.uri_reference('https://github.com/') -+ >>> validator = validators.Validator().require_presence_of( -+ ... 'scheme', 'host', 'path', -+ ... ).allow_schemes( -+ ... 'http', 'https', -+ ... ).allow_hosts( -+ ... '127.0.0.1', 'github.com', -+ ... ) -+ >>> validator.validate(uri) -+ >>> invalid_uri = rfc3986.uri_reference('imap://mail.google.com') -+ >>> validator.validate(invalid_uri) -+ Traceback (most recent call last): -+ ... -+ rfc3986.exceptions.MissingComponentError: ('path was required but -+ missing', URIReference(scheme=u'imap', authority=u'mail.google.com', -+ path=None, query=None, fragment=None), ['path']) -+ -+ """ -+ -+ COMPONENT_NAMES = frozenset([ -+ 'scheme', -+ 'userinfo', -+ 'host', -+ 'port', -+ 'path', -+ 'query', -+ 'fragment', -+ ]) -+ -+ def __init__(self): -+ """Initialize our default validations.""" -+ self.allowed_schemes = set() -+ self.allowed_hosts = set() -+ self.allowed_ports = set() -+ self.allow_password = True -+ self.required_components = { -+ 'scheme': False, -+ 'userinfo': False, -+ 'host': False, -+ 'port': False, -+ 'path': False, -+ 'query': False, -+ 'fragment': False, -+ } -+ self.validated_components = self.required_components.copy() -+ -+ def allow_schemes(self, *schemes): -+ """Require the scheme to be one of the provided schemes. -+ -+ .. versionadded:: 1.0 -+ -+ :param schemes: -+ Schemes, without ``://`` that are allowed. -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ for scheme in schemes: -+ self.allowed_schemes.add(normalizers.normalize_scheme(scheme)) -+ return self -+ -+ def allow_hosts(self, *hosts): -+ """Require the host to be one of the provided hosts. -+ -+ .. versionadded:: 1.0 -+ -+ :param hosts: -+ Hosts that are allowed. -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ for host in hosts: -+ self.allowed_hosts.add(normalizers.normalize_host(host)) -+ return self -+ -+ def allow_ports(self, *ports): -+ """Require the port to be one of the provided ports. -+ -+ .. versionadded:: 1.0 -+ -+ :param ports: -+ Ports that are allowed. -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ for port in ports: -+ port_int = int(port, base=10) -+ if 0 <= port_int <= 65535: -+ self.allowed_ports.add(port) -+ return self -+ -+ def allow_use_of_password(self): -+ """Allow passwords to be present in the URI. -+ -+ .. versionadded:: 1.0 -+ -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ self.allow_password = True -+ return self -+ -+ def forbid_use_of_password(self): -+ """Prevent passwords from being included in the URI. -+ -+ .. versionadded:: 1.0 -+ -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ self.allow_password = False -+ return self -+ -+ def check_validity_of(self, *components): -+ """Check the validity of the components provided. -+ -+ This can be specified repeatedly. -+ -+ .. versionadded:: 1.1 -+ -+ :param components: -+ Names of components from :attr:`Validator.COMPONENT_NAMES`. -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ components = [c.lower() for c in components] -+ for component in components: -+ if component not in self.COMPONENT_NAMES: -+ raise ValueError( -+ '"{}" is not a valid component'.format(component) -+ ) -+ self.validated_components.update({ -+ component: True for component in components -+ }) -+ return self -+ -+ def require_presence_of(self, *components): -+ """Require the components provided. -+ -+ This can be specified repeatedly. -+ -+ .. versionadded:: 1.0 -+ -+ :param components: -+ Names of components from :attr:`Validator.COMPONENT_NAMES`. -+ :returns: -+ The validator instance. -+ :rtype: -+ Validator -+ """ -+ components = [c.lower() for c in components] -+ for component in components: -+ if component not in self.COMPONENT_NAMES: -+ raise ValueError( -+ '"{}" is not a valid component'.format(component) -+ ) -+ self.required_components.update({ -+ component: True for component in components -+ }) -+ return self -+ -+ def validate(self, uri): -+ """Check a URI for conditions specified on this validator. -+ -+ .. versionadded:: 1.0 -+ -+ :param uri: -+ Parsed URI to validate. -+ :type uri: -+ rfc3986.uri.URIReference -+ :raises MissingComponentError: -+ When a required component is missing. -+ :raises UnpermittedComponentError: -+ When a component is not one of those allowed. -+ :raises PasswordForbidden: -+ When a password is present in the userinfo component but is -+ not permitted by configuration. -+ :raises InvalidComponentsError: -+ When a component was found to be invalid. -+ """ -+ if not self.allow_password: -+ check_password(uri) -+ -+ required_components = [ -+ component -+ for component, required in self.required_components.items() -+ if required -+ ] -+ validated_components = [ -+ component -+ for component, required in self.validated_components.items() -+ if required -+ ] -+ if required_components: -+ ensure_required_components_exist(uri, required_components) -+ if validated_components: -+ ensure_components_are_valid(uri, validated_components) -+ -+ ensure_one_of(self.allowed_schemes, uri, 'scheme') -+ ensure_one_of(self.allowed_hosts, uri, 'host') -+ ensure_one_of(self.allowed_ports, uri, 'port') -+ -+ -+def check_password(uri): -+ """Assert that there is no password present in the uri.""" -+ userinfo = uri.userinfo -+ if not userinfo: -+ return -+ credentials = userinfo.split(':', 1) -+ if len(credentials) <= 1: -+ return -+ raise exceptions.PasswordForbidden(uri) -+ -+ -+def ensure_one_of(allowed_values, uri, attribute): -+ """Assert that the uri's attribute is one of the allowed values.""" -+ value = getattr(uri, attribute) -+ if value is not None and allowed_values and value not in allowed_values: -+ raise exceptions.UnpermittedComponentError( -+ attribute, value, allowed_values, -+ ) -+ -+ -+def ensure_required_components_exist(uri, required_components): -+ """Assert that all required components are present in the URI.""" -+ missing_components = sorted([ -+ component -+ for component in required_components -+ if getattr(uri, component) is None -+ ]) -+ if missing_components: -+ raise exceptions.MissingComponentError(uri, *missing_components) -+ -+ -+def is_valid(value, matcher, require): -+ """Determine if a value is valid based on the provided matcher. -+ -+ :param str value: -+ Value to validate. -+ :param matcher: -+ Compiled regular expression to use to validate the value. -+ :param require: -+ Whether or not the value is required. -+ """ -+ if require: -+ return (value is not None -+ and matcher.match(value)) -+ -+ # require is False and value is not None -+ return value is None or matcher.match(value) -+ -+ -+def authority_is_valid(authority, host=None, require=False): -+ """Determine if the authority string is valid. -+ -+ :param str authority: -+ The authority to validate. -+ :param str host: -+ (optional) The host portion of the authority to validate. -+ :param bool require: -+ (optional) Specify if authority must not be None. -+ :returns: -+ ``True`` if valid, ``False`` otherwise -+ :rtype: -+ bool -+ """ -+ validated = is_valid(authority, misc.SUBAUTHORITY_MATCHER, require) -+ if validated and host is not None: -+ return host_is_valid(host, require) -+ return validated -+ -+ -+def host_is_valid(host, require=False): -+ """Determine if the host string is valid. -+ -+ :param str host: -+ The host to validate. -+ :param bool require: -+ (optional) Specify if host must not be None. -+ :returns: -+ ``True`` if valid, ``False`` otherwise -+ :rtype: -+ bool -+ """ -+ validated = is_valid(host, misc.HOST_MATCHER, require) -+ if validated and host is not None and misc.IPv4_MATCHER.match(host): -+ return valid_ipv4_host_address(host) -+ elif validated and host is not None and misc.IPv6_MATCHER.match(host): -+ return misc.IPv6_NO_RFC4007_MATCHER.match(host) is not None -+ return validated -+ -+ -+def scheme_is_valid(scheme, require=False): -+ """Determine if the scheme is valid. -+ -+ :param str scheme: -+ The scheme string to validate. -+ :param bool require: -+ (optional) Set to ``True`` to require the presence of a scheme. -+ :returns: -+ ``True`` if the scheme is valid. ``False`` otherwise. -+ :rtype: -+ bool -+ """ -+ return is_valid(scheme, misc.SCHEME_MATCHER, require) -+ -+ -+def path_is_valid(path, require=False): -+ """Determine if the path component is valid. -+ -+ :param str path: -+ The path string to validate. -+ :param bool require: -+ (optional) Set to ``True`` to require the presence of a path. -+ :returns: -+ ``True`` if the path is valid. ``False`` otherwise. -+ :rtype: -+ bool -+ """ -+ return is_valid(path, misc.PATH_MATCHER, require) -+ -+ -+def query_is_valid(query, require=False): -+ """Determine if the query component is valid. -+ -+ :param str query: -+ The query string to validate. -+ :param bool require: -+ (optional) Set to ``True`` to require the presence of a query. -+ :returns: -+ ``True`` if the query is valid. ``False`` otherwise. -+ :rtype: -+ bool -+ """ -+ return is_valid(query, misc.QUERY_MATCHER, require) -+ -+ -+def fragment_is_valid(fragment, require=False): -+ """Determine if the fragment component is valid. -+ -+ :param str fragment: -+ The fragment string to validate. -+ :param bool require: -+ (optional) Set to ``True`` to require the presence of a fragment. -+ :returns: -+ ``True`` if the fragment is valid. ``False`` otherwise. -+ :rtype: -+ bool -+ """ -+ return is_valid(fragment, misc.FRAGMENT_MATCHER, require) -+ -+ -+def valid_ipv4_host_address(host): -+ """Determine if the given host is a valid IPv4 address.""" -+ # If the host exists, and it might be IPv4, check each byte in the -+ # address. -+ return all([0 <= int(byte, base=10) <= 255 for byte in host.split('.')]) -+ -+ -+_COMPONENT_VALIDATORS = { -+ 'scheme': scheme_is_valid, -+ 'path': path_is_valid, -+ 'query': query_is_valid, -+ 'fragment': fragment_is_valid, -+} -+ -+_SUBAUTHORITY_VALIDATORS = set(['userinfo', 'host', 'port']) -+ -+ -+def subauthority_component_is_valid(uri, component): -+ """Determine if the userinfo, host, and port are valid.""" -+ try: -+ subauthority_dict = uri.authority_info() -+ except exceptions.InvalidAuthority: -+ return False -+ -+ # If we can parse the authority into sub-components and we're not -+ # validating the port, we can assume it's valid. -+ if component == 'host': -+ return host_is_valid(subauthority_dict['host']) -+ elif component != 'port': -+ return True -+ -+ try: -+ port = int(subauthority_dict['port']) -+ except TypeError: -+ # If the port wasn't provided it'll be None and int(None) raises a -+ # TypeError -+ return True -+ -+ return (0 <= port <= 65535) -+ -+ -+def ensure_components_are_valid(uri, validated_components): -+ """Assert that all components are valid in the URI.""" -+ invalid_components = set([]) -+ for component in validated_components: -+ if component in _SUBAUTHORITY_VALIDATORS: -+ if not subauthority_component_is_valid(uri, component): -+ invalid_components.add(component) -+ # Python's peephole optimizer means that while this continue *is* -+ # actually executed, coverage.py cannot detect that. See also, -+ # https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered -+ continue # nocov: Python 2.7, 3.3, 3.4 -+ -+ validator = _COMPONENT_VALIDATORS[component] -+ if not validator(getattr(uri, component)): -+ invalid_components.add(component) -+ -+ if invalid_components: -+ raise exceptions.InvalidComponentsError(uri, *invalid_components) -diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py -index fe5491cfda..a6ade6e905 100644 ---- a/src/pip/_vendor/urllib3/poolmanager.py -+++ b/src/pip/_vendor/urllib3/poolmanager.py -@@ -7,6 +7,7 @@ - from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool - from .connectionpool import port_by_scheme - from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown -+from .packages import six - from .packages.six.moves.urllib.parse import urljoin - from .request import RequestMethods - from .util.url import parse_url -@@ -19,7 +20,8 @@ - log = logging.getLogger(__name__) - - SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', -- 'ssl_version', 'ca_cert_dir', 'ssl_context') -+ 'ssl_version', 'ca_cert_dir', 'ssl_context', -+ 'key_password') - - # All known keyword arguments that could be provided to the pool manager, its - # pools, or the underlying connections. This is used to construct a pool key. -@@ -33,6 +35,7 @@ - 'key_block', # bool - 'key_source_address', # str - 'key_key_file', # str -+ 'key_key_password', # str - 'key_cert_file', # str - 'key_cert_reqs', # str - 'key_ca_certs', # str -@@ -47,7 +50,7 @@ - 'key__socks_options', # dict - 'key_assert_hostname', # bool or string - 'key_assert_fingerprint', # str -- 'key_server_hostname', #str -+ 'key_server_hostname', # str - ) - - #: The namedtuple class used to construct keys for the connection pool. -@@ -342,8 +345,10 @@ def urlopen(self, method, url, redirect=True, **kw): - # conn.is_same_host() which may use socket.gethostbyname() in the future. - if (retries.remove_headers_on_redirect - and not conn.is_same_host(redirect_location)): -- for header in retries.remove_headers_on_redirect: -- kw['headers'].pop(header, None) -+ headers = list(six.iterkeys(kw['headers'])) -+ for header in headers: -+ if header.lower() in retries.remove_headers_on_redirect: -+ kw['headers'].pop(header, None) - - try: - retries = retries.increment(method, url, response=response, _pool=conn) -diff --git a/src/pip/_vendor/urllib3/response.py b/src/pip/_vendor/urllib3/response.py -index c112690b0a..4f857932c5 100644 ---- a/src/pip/_vendor/urllib3/response.py -+++ b/src/pip/_vendor/urllib3/response.py -@@ -6,6 +6,11 @@ - from socket import timeout as SocketTimeout - from socket import error as SocketError - -+try: -+ import brotli -+except ImportError: -+ brotli = None -+ - from ._collections import HTTPHeaderDict - from .exceptions import ( - BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, -@@ -90,6 +95,25 @@ def decompress(self, data): - self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) - - -+if brotli is not None: -+ class BrotliDecoder(object): -+ # Supports both 'brotlipy' and 'Brotli' packages -+ # since they share an import name. The top branches -+ # are for 'brotlipy' and bottom branches for 'Brotli' -+ def __init__(self): -+ self._obj = brotli.Decompressor() -+ -+ def decompress(self, data): -+ if hasattr(self._obj, 'decompress'): -+ return self._obj.decompress(data) -+ return self._obj.process(data) -+ -+ def flush(self): -+ if hasattr(self._obj, 'flush'): -+ return self._obj.flush() -+ return b'' -+ -+ - class MultiDecoder(object): - """ - From RFC7231: -@@ -118,6 +142,9 @@ def _get_decoder(mode): - if mode == 'gzip': - return GzipDecoder() - -+ if brotli is not None and mode == 'br': -+ return BrotliDecoder() -+ - return DeflateDecoder() - - -@@ -155,6 +182,8 @@ class is also compatible with the Python standard library's :mod:`io` - """ - - CONTENT_DECODERS = ['gzip', 'deflate'] -+ if brotli is not None: -+ CONTENT_DECODERS += ['br'] - REDIRECT_STATUSES = [301, 302, 303, 307, 308] - - def __init__(self, body='', headers=None, status=0, version=0, reason=None, -@@ -311,24 +340,32 @@ def _init_decoder(self): - if content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) - elif ',' in content_encoding: -- encodings = [e.strip() for e in content_encoding.split(',') if e.strip() in self.CONTENT_DECODERS] -+ encodings = [ -+ e.strip() for e in content_encoding.split(',') -+ if e.strip() in self.CONTENT_DECODERS] - if len(encodings): - self._decoder = _get_decoder(content_encoding) - -+ DECODER_ERROR_CLASSES = (IOError, zlib.error) -+ if brotli is not None: -+ DECODER_ERROR_CLASSES += (brotli.error,) -+ - def _decode(self, data, decode_content, flush_decoder): - """ - Decode the data passed in and potentially flush the decoder. - """ -+ if not decode_content: -+ return data -+ - try: -- if decode_content and self._decoder: -+ if self._decoder: - data = self._decoder.decompress(data) -- except (IOError, zlib.error) as e: -+ except self.DECODER_ERROR_CLASSES as e: - content_encoding = self.headers.get('content-encoding', '').lower() - raise DecodeError( - "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, e) -- -- if flush_decoder and decode_content: -+ if flush_decoder: - data += self._flush_decoder() - - return data -@@ -508,9 +545,10 @@ def from_httplib(ResponseCls, r, **response_kw): - headers = r.msg - - if not isinstance(headers, HTTPHeaderDict): -- if PY3: # Python 3 -+ if PY3: - headers = HTTPHeaderDict(headers.items()) -- else: # Python 2 -+ else: -+ # Python 2.7 - headers = HTTPHeaderDict.from_httplib(headers) - - # HTTPResponse objects in Python 3 don't have a .strict attribute -@@ -703,3 +741,20 @@ def geturl(self): - return self.retries.history[-1].redirect_location - else: - return self._request_url -+ -+ def __iter__(self): -+ buffer = [b""] -+ for chunk in self.stream(decode_content=True): -+ if b"\n" in chunk: -+ chunk = chunk.split(b"\n") -+ yield b"".join(buffer) + chunk[0] + b"\n" -+ for x in chunk[1:-1]: -+ yield x + b"\n" -+ if chunk[-1]: -+ buffer = [chunk[-1]] -+ else: -+ buffer = [] -+ else: -+ buffer.append(chunk) -+ if buffer: -+ yield b"".join(buffer) -diff --git a/src/pip/_vendor/urllib3/util/__init__.py b/src/pip/_vendor/urllib3/util/__init__.py -index 2f2770b622..2914bb468b 100644 ---- a/src/pip/_vendor/urllib3/util/__init__.py -+++ b/src/pip/_vendor/urllib3/util/__init__.py -@@ -12,6 +12,7 @@ - resolve_cert_reqs, - resolve_ssl_version, - ssl_wrap_socket, -+ PROTOCOL_TLS, - ) - from .timeout import ( - current_time, -@@ -35,6 +36,7 @@ - 'IS_PYOPENSSL', - 'IS_SECURETRANSPORT', - 'SSLContext', -+ 'PROTOCOL_TLS', - 'Retry', - 'Timeout', - 'Url', -diff --git a/src/pip/_vendor/urllib3/util/request.py b/src/pip/_vendor/urllib3/util/request.py -index 3ddfcd5594..280b8530c6 100644 ---- a/src/pip/_vendor/urllib3/util/request.py -+++ b/src/pip/_vendor/urllib3/util/request.py -@@ -5,6 +5,13 @@ - from ..exceptions import UnrewindableBodyError - - ACCEPT_ENCODING = 'gzip,deflate' -+try: -+ import brotli as _unused_module_brotli # noqa: F401 -+except ImportError: -+ pass -+else: -+ ACCEPT_ENCODING += ',br' -+ - _FAILEDTELL = object() - - -diff --git a/src/pip/_vendor/urllib3/util/retry.py b/src/pip/_vendor/urllib3/util/retry.py -index e7d0abd610..02429ee8e4 100644 ---- a/src/pip/_vendor/urllib3/util/retry.py -+++ b/src/pip/_vendor/urllib3/util/retry.py -@@ -179,7 +179,8 @@ def __init__(self, total=10, connect=None, read=None, redirect=None, status=None - self.raise_on_status = raise_on_status - self.history = history or tuple() - self.respect_retry_after_header = respect_retry_after_header -- self.remove_headers_on_redirect = remove_headers_on_redirect -+ self.remove_headers_on_redirect = frozenset([ -+ h.lower() for h in remove_headers_on_redirect]) - - def new(self, **kw): - params = dict( -diff --git a/src/pip/_vendor/urllib3/util/ssl_.py b/src/pip/_vendor/urllib3/util/ssl_.py -index dfc553ff41..fbdef65d76 100644 ---- a/src/pip/_vendor/urllib3/util/ssl_.py -+++ b/src/pip/_vendor/urllib3/util/ssl_.py -@@ -2,13 +2,14 @@ - import errno - import warnings - import hmac --import socket -+import re - - from binascii import hexlify, unhexlify - from hashlib import md5, sha1, sha256 - - from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning - from ..packages import six -+from ..packages.rfc3986 import abnf_regexp - - - SSLContext = None -@@ -40,14 +41,33 @@ def _const_compare_digest_backport(a, b): - _const_compare_digest = getattr(hmac, 'compare_digest', - _const_compare_digest_backport) - -+# Borrow rfc3986's regular expressions for IPv4 -+# and IPv6 addresses for use in is_ipaddress() -+_IP_ADDRESS_REGEX = re.compile( -+ r'^(?:%s|%s|%s)$' % ( -+ abnf_regexp.IPv4_RE, -+ abnf_regexp.IPv6_RE, -+ abnf_regexp.IPv6_ADDRZ_RFC4007_RE -+ ) -+) - - try: # Test for SSL features - import ssl -- from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 -+ from ssl import wrap_socket, CERT_REQUIRED - from ssl import HAS_SNI # Has SNI? - except ImportError: - pass - -+try: # Platform-specific: Python 3.6 -+ from ssl import PROTOCOL_TLS -+ PROTOCOL_SSLv23 = PROTOCOL_TLS -+except ImportError: -+ try: -+ from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS -+ PROTOCOL_SSLv23 = PROTOCOL_TLS -+ except ImportError: -+ PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 -+ - - try: - from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION -@@ -56,25 +76,6 @@ def _const_compare_digest_backport(a, b): - OP_NO_COMPRESSION = 0x20000 - - --# Python 2.7 doesn't have inet_pton on non-Linux so we fallback on inet_aton in --# those cases. This means that we can only detect IPv4 addresses in this case. --if hasattr(socket, 'inet_pton'): -- inet_pton = socket.inet_pton --else: -- # Maybe we can use ipaddress if the user has urllib3[secure]? -- try: -- from pip._vendor import ipaddress -- -- def inet_pton(_, host): -- if isinstance(host, bytes): -- host = host.decode('ascii') -- return ipaddress.ip_address(host) -- -- except ImportError: # Platform-specific: Non-Linux -- def inet_pton(_, host): -- return socket.inet_aton(host) -- -- - # A secure default. - # Sources for more information on TLS ciphers: - # -@@ -83,37 +84,35 @@ def inet_pton(_, host): - # - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ - # - # The general intent is: --# - Prefer TLS 1.3 cipher suites - # - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), - # - prefer ECDHE over DHE for better performance, - # - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and - # security, - # - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, --# - disable NULL authentication, MD5 MACs and DSS for security reasons. -+# - disable NULL authentication, MD5 MACs, DSS, and other -+# insecure ciphers for security reasons. -+# - NOTE: TLS 1.3 cipher suites are managed through a different interface -+# not exposed by CPython (yet!) and are enabled by default if they're available. - DEFAULT_CIPHERS = ':'.join([ -- 'TLS13-AES-256-GCM-SHA384', -- 'TLS13-CHACHA20-POLY1305-SHA256', -- 'TLS13-AES-128-GCM-SHA256', -+ 'ECDHE+AESGCM', -+ 'ECDHE+CHACHA20', -+ 'DHE+AESGCM', -+ 'DHE+CHACHA20', - 'ECDH+AESGCM', -- 'ECDH+CHACHA20', - 'DH+AESGCM', -- 'DH+CHACHA20', -- 'ECDH+AES256', -- 'DH+AES256', -- 'ECDH+AES128', -+ 'ECDH+AES', - 'DH+AES', - 'RSA+AESGCM', - 'RSA+AES', - '!aNULL', - '!eNULL', - '!MD5', -+ '!DSS', - ]) - - try: - from ssl import SSLContext # Modern SSL? - except ImportError: -- import sys -- - class SSLContext(object): # Platform-specific: Python 2 - def __init__(self, protocol_version): - self.protocol = protocol_version -@@ -199,7 +198,7 @@ def resolve_cert_reqs(candidate): - constant which can directly be passed to wrap_socket. - """ - if candidate is None: -- return CERT_NONE -+ return CERT_REQUIRED - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) -@@ -215,7 +214,7 @@ def resolve_ssl_version(candidate): - like resolve_cert_reqs - """ - if candidate is None: -- return PROTOCOL_SSLv23 -+ return PROTOCOL_TLS - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) -@@ -261,7 +260,7 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, - Constructed SSLContext object with specified options - :rtype: SSLContext - """ -- context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) -+ context = SSLContext(ssl_version or PROTOCOL_TLS) - - context.set_ciphers(ciphers or DEFAULT_CIPHERS) - -@@ -291,7 +290,7 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, - def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None, ciphers=None, ssl_context=None, -- ca_cert_dir=None): -+ ca_cert_dir=None, key_password=None): - """ - All arguments except for server_hostname, ssl_context, and ca_cert_dir have - the same meaning as they do when using :func:`ssl.wrap_socket`. -@@ -307,6 +306,8 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - A directory containing CA certificates in multiple separate files, as - supported by OpenSSL's -CApath flag or the capath argument to - SSLContext.load_verify_locations(). -+ :param key_password: -+ Optional password if the keyfile is encrypted. - """ - context = ssl_context - if context is None: -@@ -327,12 +328,22 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - if e.errno == errno.ENOENT: - raise SSLError(e) - raise -- elif getattr(context, 'load_default_certs', None) is not None: -+ -+ elif ssl_context is None and hasattr(context, 'load_default_certs'): - # try to load OS default certs; works well on Windows (require Python3.4+) - context.load_default_certs() - -+ # Attempt to detect if we get the goofy behavior of the -+ # keyfile being encrypted and OpenSSL asking for the -+ # passphrase via the terminal and instead error out. -+ if keyfile and key_password is None and _is_key_file_encrypted(keyfile): -+ raise SSLError("Client private key is encrypted, password is required") -+ - if certfile: -- context.load_cert_chain(certfile, keyfile) -+ if key_password is None: -+ context.load_cert_chain(certfile, keyfile) -+ else: -+ context.load_cert_chain(certfile, keyfile, key_password) - - # If we detect server_hostname is an IP address then the SNI - # extension should not be used according to RFC3546 Section 3.1 -@@ -358,7 +369,8 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - - - def is_ipaddress(hostname): -- """Detects whether the hostname given is an IP address. -+ """Detects whether the hostname given is an IPv4 or IPv6 address. -+ Also detects IPv6 addresses with Zone IDs. - - :param str hostname: Hostname to examine. - :return: True if the hostname is an IP address, False otherwise. -@@ -366,16 +378,15 @@ def is_ipaddress(hostname): - if six.PY3 and isinstance(hostname, bytes): - # IDN A-label bytes are ASCII compatible. - hostname = hostname.decode('ascii') -+ return _IP_ADDRESS_REGEX.match(hostname) is not None - -- families = [socket.AF_INET] -- if hasattr(socket, 'AF_INET6'): -- families.append(socket.AF_INET6) - -- for af in families: -- try: -- inet_pton(af, hostname) -- except (socket.error, ValueError, OSError): -- pass -- else: -- return True -+def _is_key_file_encrypted(key_file): -+ """Detects if a key file is encrypted or not.""" -+ with open(key_file, 'r') as f: -+ for line in f: -+ # Look for Proc-Type: 4,ENCRYPTED -+ if 'ENCRYPTED' in line: -+ return True -+ - return False -diff --git a/src/pip/_vendor/urllib3/util/timeout.py b/src/pip/_vendor/urllib3/util/timeout.py -index cec817e6ef..a4d004a848 100644 ---- a/src/pip/_vendor/urllib3/util/timeout.py -+++ b/src/pip/_vendor/urllib3/util/timeout.py -@@ -131,7 +131,8 @@ def _validate_timeout(cls, value, name): - raise ValueError("Attempted to set %s timeout to %s, but the " - "timeout cannot be set to a value less " - "than or equal to 0." % (name, value)) -- except TypeError: # Python 3 -+ except TypeError: -+ # Python 3 - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) - -diff --git a/src/pip/_vendor/urllib3/util/url.py b/src/pip/_vendor/urllib3/util/url.py -index 6b6f9968d7..aefa119b59 100644 ---- a/src/pip/_vendor/urllib3/util/url.py -+++ b/src/pip/_vendor/urllib3/util/url.py -@@ -1,7 +1,12 @@ - from __future__ import absolute_import -+import re - from collections import namedtuple - - from ..exceptions import LocationParseError -+from ..packages import six, rfc3986 -+from ..packages.rfc3986.exceptions import RFC3986Exception, ValidationError -+from ..packages.rfc3986.validators import Validator -+from ..packages.rfc3986 import abnf_regexp, normalizers, compat, misc - - - url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] -@@ -10,10 +15,16 @@ - # urllib3 infers URLs without a scheme (None) to be http. - NORMALIZABLE_SCHEMES = ('http', 'https', None) - -+# Regex for detecting URLs with schemes. RFC 3986 Section 3.1 -+SCHEME_REGEX = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+\-]*:|/)") -+ -+PATH_CHARS = abnf_regexp.UNRESERVED_CHARS_SET | abnf_regexp.SUB_DELIMITERS_SET | {':', '@', '/'} -+QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {'?'} -+ - - class Url(namedtuple('Url', url_attrs)): - """ -- Datastructure for representing an HTTP URL. Used as a return value for -+ Data structure for representing an HTTP URL. Used as a return value for - :func:`parse_url`. Both the scheme and host are normalized as they are - both case-insensitive according to RFC 3986. - """ -@@ -23,10 +34,8 @@ def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, - query=None, fragment=None): - if path and not path.startswith('/'): - path = '/' + path -- if scheme: -+ if scheme is not None: - scheme = scheme.lower() -- if host and scheme in NORMALIZABLE_SCHEMES: -- host = host.lower() - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, - query, fragment) - -@@ -72,23 +81,23 @@ def url(self): - 'http://username:password@host.com:80/path?query#fragment' - """ - scheme, auth, host, port, path, query, fragment = self -- url = '' -+ url = u'' - - # We use "is not None" we want things to happen with empty strings (or 0 port) - if scheme is not None: -- url += scheme + '://' -+ url += scheme + u'://' - if auth is not None: -- url += auth + '@' -+ url += auth + u'@' - if host is not None: - url += host - if port is not None: -- url += ':' + str(port) -+ url += u':' + str(port) - if path is not None: - url += path - if query is not None: -- url += '?' + query -+ url += u'?' + query - if fragment is not None: -- url += '#' + fragment -+ url += u'#' + fragment - - return url - -@@ -98,6 +107,8 @@ def __str__(self): - - def split_first(s, delims): - """ -+ .. deprecated:: 1.25 -+ - Given a string and an iterable of delimiters, split on the first found - delimiter. Return two split parts and the matched delimiter. - -@@ -129,10 +140,44 @@ def split_first(s, delims): - return s[:min_idx], s[min_idx + 1:], min_delim - - -+def _encode_invalid_chars(component, allowed_chars, encoding='utf-8'): -+ """Percent-encodes a URI component without reapplying -+ onto an already percent-encoded component. Based on -+ rfc3986.normalizers.encode_component() -+ """ -+ if component is None: -+ return component -+ -+ # Try to see if the component we're encoding is already percent-encoded -+ # so we can skip all '%' characters but still encode all others. -+ percent_encodings = len(normalizers.PERCENT_MATCHER.findall( -+ compat.to_str(component, encoding))) -+ -+ uri_bytes = component.encode('utf-8', 'surrogatepass') -+ is_percent_encoded = percent_encodings == uri_bytes.count(b'%') -+ -+ encoded_component = bytearray() -+ -+ for i in range(0, len(uri_bytes)): -+ # Will return a single character bytestring on both Python 2 & 3 -+ byte = uri_bytes[i:i+1] -+ byte_ord = ord(byte) -+ if ((is_percent_encoded and byte == b'%') -+ or (byte_ord < 128 and byte.decode() in allowed_chars)): -+ encoded_component.extend(byte) -+ continue -+ encoded_component.extend('%{0:02x}'.format(byte_ord).encode().upper()) -+ -+ return encoded_component.decode(encoding) -+ -+ - def parse_url(url): - """ - Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is - performed to parse incomplete urls. Fields not provided will be None. -+ This parser is RFC 3986 compliant. -+ -+ :param str url: URL to parse into a :class:`.Url` namedtuple. - - Partly backwards-compatible with :mod:`urlparse`. - -@@ -145,81 +190,95 @@ def parse_url(url): - >>> parse_url('/foo?bar') - Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) - """ -- -- # While this code has overlap with stdlib's urlparse, it is much -- # simplified for our needs and less annoying. -- # Additionally, this implementations does silly things to be optimal -- # on CPython. -- - if not url: - # Empty - return Url() - -- scheme = None -- auth = None -- host = None -- port = None -- path = None -- fragment = None -- query = None -- -- # Scheme -- if '://' in url: -- scheme, url = url.split('://', 1) -- -- # Find the earliest Authority Terminator -- # (http://tools.ietf.org/html/rfc3986#section-3.2) -- url, path_, delim = split_first(url, ['/', '?', '#']) -- -- if delim: -- # Reassemble the path -- path = delim + path_ -- -- # Auth -- if '@' in url: -- # Last '@' denotes end of auth part -- auth, url = url.rsplit('@', 1) -- -- # IPv6 -- if url and url[0] == '[': -- host, url = url.split(']', 1) -- host += ']' -- -- # Port -- if ':' in url: -- _host, port = url.split(':', 1) -- -- if not host: -- host = _host -- -- if port: -- # If given, ports must be integers. No whitespace, no plus or -- # minus prefixes, no non-integer digits such as ^2 (superscript). -- if not port.isdigit(): -- raise LocationParseError(url) -- try: -- port = int(port) -- except ValueError: -- raise LocationParseError(url) -- else: -- # Blank ports are cool, too. (rfc3986#section-3.2.3) -- port = None -+ is_string = not isinstance(url, six.binary_type) - -- elif not host and url: -- host = url -+ # RFC 3986 doesn't like URLs that have a host but don't start -+ # with a scheme and we support URLs like that so we need to -+ # detect that problem and add an empty scheme indication. -+ # We don't get hurt on path-only URLs here as it's stripped -+ # off and given an empty scheme anyways. -+ if not SCHEME_REGEX.search(url): -+ url = "//" + url - -+ def idna_encode(name): -+ if name and any([ord(x) > 128 for x in name]): -+ try: -+ from pip._vendor import idna -+ except ImportError: -+ raise LocationParseError("Unable to parse URL without the 'idna' module") -+ try: -+ return idna.encode(name.lower(), strict=True, std3_rules=True) -+ except idna.IDNAError: -+ raise LocationParseError(u"Name '%s' is not a valid IDNA label" % name) -+ return name -+ -+ try: -+ split_iri = misc.IRI_MATCHER.match(compat.to_str(url)).groupdict() -+ iri_ref = rfc3986.IRIReference( -+ split_iri['scheme'], split_iri['authority'], -+ _encode_invalid_chars(split_iri['path'], PATH_CHARS), -+ _encode_invalid_chars(split_iri['query'], QUERY_CHARS), -+ _encode_invalid_chars(split_iri['fragment'], FRAGMENT_CHARS) -+ ) -+ has_authority = iri_ref.authority is not None -+ uri_ref = iri_ref.encode(idna_encoder=idna_encode) -+ except (ValueError, RFC3986Exception): -+ return six.raise_from(LocationParseError(url), None) -+ -+ # rfc3986 strips the authority if it's invalid -+ if has_authority and uri_ref.authority is None: -+ raise LocationParseError(url) -+ -+ # Only normalize schemes we understand to not break http+unix -+ # or other schemes that don't follow RFC 3986. -+ if uri_ref.scheme is None or uri_ref.scheme.lower() in NORMALIZABLE_SCHEMES: -+ uri_ref = uri_ref.normalize() -+ -+ # Validate all URIReference components and ensure that all -+ # components that were set before are still set after -+ # normalization has completed. -+ validator = Validator() -+ try: -+ validator.check_validity_of( -+ *validator.COMPONENT_NAMES -+ ).validate(uri_ref) -+ except ValidationError: -+ return six.raise_from(LocationParseError(url), None) -+ -+ # For the sake of backwards compatibility we put empty -+ # string values for path if there are any defined values -+ # beyond the path in the URL. -+ # TODO: Remove this when we break backwards compatibility. -+ path = uri_ref.path - if not path: -- return Url(scheme, auth, host, port, path, query, fragment) -- -- # Fragment -- if '#' in path: -- path, fragment = path.split('#', 1) -- -- # Query -- if '?' in path: -- path, query = path.split('?', 1) -- -- return Url(scheme, auth, host, port, path, query, fragment) -+ if (uri_ref.query is not None -+ or uri_ref.fragment is not None): -+ path = "" -+ else: -+ path = None -+ -+ # Ensure that each part of the URL is a `str` for -+ # backwards compatibility. -+ def to_input_type(x): -+ if x is None: -+ return None -+ elif not is_string and not isinstance(x, six.binary_type): -+ return x.encode('utf-8') -+ return x -+ -+ return Url( -+ scheme=to_input_type(uri_ref.scheme), -+ auth=to_input_type(uri_ref.userinfo), -+ host=to_input_type(uri_ref.host), -+ port=int(uri_ref.port) if uri_ref.port is not None else None, -+ path=to_input_type(path), -+ query=to_input_type(uri_ref.query), -+ fragment=to_input_type(uri_ref.fragment) -+ ) - - - def get_host(url): -diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt -index db003aced7..d81c503e3c 100644 ---- a/src/pip/_vendor/vendor.txt -+++ b/src/pip/_vendor/vendor.txt -@@ -16,7 +16,7 @@ requests==2.21.0 - certifi==2018.11.29 - chardet==3.0.4 - idna==2.8 -- urllib3==1.24.1 -+ urllib3==1.25.3 - retrying==1.3.3 - setuptools==40.6.3 - six==1.12.0 diff --git a/urllib3-CVE-2025-50181.patch b/urllib3-CVE-2025-50181.patch new file mode 100644 index 0000000..05e6353 --- /dev/null +++ b/urllib3-CVE-2025-50181.patch @@ -0,0 +1,51 @@ +From b3d543d7e16af844394316360ef1bf0b9d10f1b1 Mon Sep 17 00:00:00 2001 +From: Illia Volochii +Date: Wed, 18 Jun 2025 16:25:01 +0300 +Subject: [PATCH] Security fix for CVE-2025-50181 + +Co-authored-by: Seth Michael Larson +Co-authored-by: Quentin Pradet +Co-authored-by: Seth Michael Larson +--- + src/pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py +index fb51bf7..a8de7c6 100644 +--- a/src/pip/_vendor/urllib3/poolmanager.py ++++ b/src/pip/_vendor/urllib3/poolmanager.py +@@ -170,6 +170,22 @@ class PoolManager(RequestMethods): + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) ++ if "retries" in connection_pool_kw: ++ retries = connection_pool_kw["retries"] ++ if not isinstance(retries, Retry): ++ # When Retry is initialized, raise_on_redirect is based ++ # on a redirect boolean value. ++ # But requests made via a pool manager always set ++ # redirect to False, and raise_on_redirect always ends ++ # up being False consequently. ++ # Here we fix the issue by setting raise_on_redirect to ++ # a value needed by the pool manager without considering ++ # the redirect boolean. ++ raise_on_redirect = retries is not False ++ retries = Retry.from_int(retries, redirect=False) ++ retries.raise_on_redirect = raise_on_redirect ++ connection_pool_kw = connection_pool_kw.copy() ++ connection_pool_kw["retries"] = retries + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools) + +@@ -389,7 +405,7 @@ class PoolManager(RequestMethods): + kw["body"] = None + kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() + +- retries = kw.get("retries") ++ retries = kw.get("retries", response.retries) + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + +-- +2.51.0 + diff --git a/vendor_meta.sh b/vendor_meta.sh deleted file mode 100755 index 3ae5204..0000000 --- a/vendor_meta.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/bash -eu - -if [ $# -ne 1 ]; then - echo "Usage: ./vendor_meta.sh pip-10.0.0/src/pip/_vendor/vendor.txt" - exit 1 -fi - -licenses='' - -while read req; do - req=$(echo $req | cut -f1 -d' ') - name=$(echo $req | cut -f1 -d'=') - version=$(echo $req | cut -f3 -d'=' | tr -d '\r') - echo "Provides: bundled(python%{1}dist($name)) = $version" - license="$(pyp2rpm -v ${version} --no-venv ${name} | grep '^License:' | sed -e 's/License:\s*//')" - licenses="$licenses\n$name: $license" -done < $1 - -echo -echo -echo -e "$licenses"