summaryrefslogtreecommitdiffstats
path: root/contrib/python/pip
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-08-14 13:20:58 +0300
committerrobot-piglet <[email protected]>2025-08-14 13:31:38 +0300
commit88a7de53eef5cfac7f3d5e42e703eccfc5556786 (patch)
tree4b05d57fc76f08bec33f7e61504c73da20864dc4 /contrib/python/pip
parent69a78f95307f8447262904dafb391cb63cd6582b (diff)
Intermediate changes
commit_hash:b651b859acd6bc64f3bdea3bf5115843fba67b82
Diffstat (limited to 'contrib/python/pip')
-rw-r--r--contrib/python/pip/.dist-info/METADATA30
-rw-r--r--contrib/python/pip/AUTHORS.txt12
-rw-r--r--contrib/python/pip/COPYING14
-rw-r--r--contrib/python/pip/LICENSE21
-rw-r--r--contrib/python/pip/LICENSE-HEADER3
-rw-r--r--contrib/python/pip/LICENSE.APACHE177
-rw-r--r--contrib/python/pip/LICENSE.BSD23
-rw-r--r--contrib/python/pip/LICENSE.md31
-rw-r--r--contrib/python/pip/pip/__init__.py6
-rw-r--r--contrib/python/pip/pip/_internal/__init__.py4
-rw-r--r--contrib/python/pip/pip/_internal/build_env.py212
-rw-r--r--contrib/python/pip/pip/_internal/cache.py30
-rw-r--r--contrib/python/pip/pip/_internal/cli/autocompletion.py17
-rw-r--r--contrib/python/pip/pip/_internal/cli/base_command.py25
-rw-r--r--contrib/python/pip/pip/_internal/cli/cmdoptions.py23
-rw-r--r--contrib/python/pip/pip/_internal/cli/command_context.py7
-rw-r--r--contrib/python/pip/pip/_internal/cli/index_command.py20
-rw-r--r--contrib/python/pip/pip/_internal/cli/main.py5
-rw-r--r--contrib/python/pip/pip/_internal/cli/main_parser.py7
-rw-r--r--contrib/python/pip/pip/_internal/cli/parser.py48
-rw-r--r--contrib/python/pip/pip/_internal/cli/progress_bars.py31
-rw-r--r--contrib/python/pip/pip/_internal/cli/req_command.py28
-rw-r--r--contrib/python/pip/pip/_internal/cli/spinners.py86
-rw-r--r--contrib/python/pip/pip/_internal/commands/__init__.py8
-rw-r--r--contrib/python/pip/pip/_internal/commands/cache.py33
-rw-r--r--contrib/python/pip/pip/_internal/commands/check.py3
-rw-r--r--contrib/python/pip/pip/_internal/commands/completion.py3
-rw-r--r--contrib/python/pip/pip/_internal/commands/configuration.py44
-rw-r--r--contrib/python/pip/pip/_internal/commands/debug.py14
-rw-r--r--contrib/python/pip/pip/_internal/commands/download.py5
-rw-r--r--contrib/python/pip/pip/_internal/commands/freeze.py5
-rw-r--r--contrib/python/pip/pip/_internal/commands/hash.py3
-rw-r--r--contrib/python/pip/pip/_internal/commands/help.py3
-rw-r--r--contrib/python/pip/pip/_internal/commands/index.py24
-rw-r--r--contrib/python/pip/pip/_internal/commands/inspect.py8
-rw-r--r--contrib/python/pip/pip/_internal/commands/install.py83
-rw-r--r--contrib/python/pip/pip/_internal/commands/list.py61
-rw-r--r--contrib/python/pip/pip/_internal/commands/lock.py3
-rw-r--r--contrib/python/pip/pip/_internal/commands/search.py26
-rw-r--r--contrib/python/pip/pip/_internal/commands/show.py25
-rw-r--r--contrib/python/pip/pip/_internal/commands/uninstall.py3
-rw-r--r--contrib/python/pip/pip/_internal/commands/wheel.py5
-rw-r--r--contrib/python/pip/pip/_internal/configuration.py64
-rw-r--r--contrib/python/pip/pip/_internal/distributions/base.py10
-rw-r--r--contrib/python/pip/pip/_internal/distributions/installed.py12
-rw-r--r--contrib/python/pip/pip/_internal/distributions/sdist.py33
-rw-r--r--contrib/python/pip/pip/_internal/distributions/wheel.py10
-rw-r--r--contrib/python/pip/pip/_internal/exceptions.py97
-rw-r--r--contrib/python/pip/pip/_internal/index/collector.py53
-rw-r--r--contrib/python/pip/pip/_internal/index/package_finder.py131
-rw-r--r--contrib/python/pip/pip/_internal/index/sources.py31
-rw-r--r--contrib/python/pip/pip/_internal/locations/__init__.py34
-rw-r--r--contrib/python/pip/pip/_internal/locations/_distutils.py23
-rw-r--r--contrib/python/pip/pip/_internal/locations/_sysconfig.py9
-rw-r--r--contrib/python/pip/pip/_internal/locations/base.py7
-rw-r--r--contrib/python/pip/pip/_internal/main.py4
-rw-r--r--contrib/python/pip/pip/_internal/metadata/__init__.py14
-rw-r--r--contrib/python/pip/pip/_internal/metadata/_json.py9
-rw-r--r--contrib/python/pip/pip/_internal/metadata/base.py49
-rw-r--r--contrib/python/pip/pip/_internal/metadata/importlib/_compat.py10
-rw-r--r--contrib/python/pip/pip/_internal/metadata/importlib/_dists.py29
-rw-r--r--contrib/python/pip/pip/_internal/metadata/importlib/_envs.py15
-rw-r--r--contrib/python/pip/pip/_internal/metadata/pkg_resources.py25
-rw-r--r--contrib/python/pip/pip/_internal/models/direct_url.py45
-rw-r--r--contrib/python/pip/pip/_internal/models/format_control.py10
-rw-r--r--contrib/python/pip/pip/_internal/models/installation_report.py7
-rw-r--r--contrib/python/pip/pip/_internal/models/link.py73
-rw-r--r--contrib/python/pip/pip/_internal/models/pylock.py49
-rw-r--r--contrib/python/pip/pip/_internal/models/search_scope.py13
-rw-r--r--contrib/python/pip/pip/_internal/models/selection_prefs.py6
-rw-r--r--contrib/python/pip/pip/_internal/models/target_python.py19
-rw-r--r--contrib/python/pip/pip/_internal/models/wheel.py12
-rw-r--r--contrib/python/pip/pip/_internal/network/auth.py42
-rw-r--r--contrib/python/pip/pip/_internal/network/cache.py28
-rw-r--r--contrib/python/pip/pip/_internal/network/download.py308
-rw-r--r--contrib/python/pip/pip/_internal/network/lazy_wheel.py17
-rw-r--r--contrib/python/pip/pip/_internal/network/session.py59
-rw-r--r--contrib/python/pip/pip/_internal/network/utils.py4
-rw-r--r--contrib/python/pip/pip/_internal/network/xmlrpc.py4
-rw-r--r--contrib/python/pip/pip/_internal/operations/build/build_tracker.py18
-rw-r--r--contrib/python/pip/pip/_internal/operations/build/wheel.py5
-rw-r--r--contrib/python/pip/pip/_internal/operations/build/wheel_editable.py5
-rw-r--r--contrib/python/pip/pip/_internal/operations/build/wheel_legacy.py17
-rw-r--r--contrib/python/pip/pip/_internal/operations/check.py47
-rw-r--r--contrib/python/pip/pip/_internal/operations/freeze.py21
-rw-r--r--contrib/python/pip/pip/_internal/operations/install/editable_legacy.py8
-rw-r--r--contrib/python/pip/pip/_internal/operations/install/wheel.py90
-rw-r--r--contrib/python/pip/pip/_internal/operations/prepare.py65
-rw-r--r--contrib/python/pip/pip/_internal/pyproject.py17
-rw-r--r--contrib/python/pip/pip/_internal/req/__init__.py22
-rw-r--r--contrib/python/pip/pip/_internal/req/constructors.py64
-rw-r--r--contrib/python/pip/pip/_internal/req/req_dependency_group.py18
-rw-r--r--contrib/python/pip/pip/_internal/req/req_file.py67
-rw-r--r--contrib/python/pip/pip/_internal/req/req_install.py71
-rw-r--r--contrib/python/pip/pip/_internal/req/req_set.py9
-rw-r--r--contrib/python/pip/pip/_internal/req/req_uninstall.py37
-rw-r--r--contrib/python/pip/pip/_internal/resolution/base.py6
-rw-r--r--contrib/python/pip/pip/_internal/resolution/legacy/resolver.py41
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/base.py29
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/candidates.py55
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/factory.py91
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/found_candidates.py20
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/provider.py35
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/reporter.py8
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/requirements.py14
-rw-r--r--contrib/python/pip/pip/_internal/resolution/resolvelib/resolver.py62
-rw-r--r--contrib/python/pip/pip/_internal/self_outdated_check.py14
-rw-r--r--contrib/python/pip/pip/_internal/utils/appdirs.py3
-rw-r--r--contrib/python/pip/pip/_internal/utils/compat.py8
-rw-r--r--contrib/python/pip/pip/_internal/utils/compatibility_tags.py33
-rw-r--r--contrib/python/pip/pip/_internal/utils/deprecation.py20
-rw-r--r--contrib/python/pip/pip/_internal/utils/direct_url_helpers.py4
-rw-r--r--contrib/python/pip/pip/_internal/utils/egg_link.py11
-rw-r--r--contrib/python/pip/pip/_internal/utils/entrypoints.py5
-rw-r--r--contrib/python/pip/pip/_internal/utils/filesystem.py13
-rw-r--r--contrib/python/pip/pip/_internal/utils/filetypes.py10
-rw-r--r--contrib/python/pip/pip/_internal/utils/glibc.py11
-rw-r--r--contrib/python/pip/pip/_internal/utils/hashes.py15
-rw-r--r--contrib/python/pip/pip/_internal/utils/logging.py13
-rw-r--r--contrib/python/pip/pip/_internal/utils/misc.py82
-rw-r--r--contrib/python/pip/pip/_internal/utils/packaging.py5
-rw-r--r--contrib/python/pip/pip/_internal/utils/retry.py11
-rw-r--r--contrib/python/pip/pip/_internal/utils/setuptools_build.py22
-rw-r--r--contrib/python/pip/pip/_internal/utils/subprocess.py37
-rw-r--r--contrib/python/pip/pip/_internal/utils/temp_dir.py22
-rw-r--r--contrib/python/pip/pip/_internal/utils/unpacking.py10
-rw-r--r--contrib/python/pip/pip/_internal/utils/urls.py2
-rw-r--r--contrib/python/pip/pip/_internal/utils/virtualenv.py5
-rw-r--r--contrib/python/pip/pip/_internal/utils/wheel.py7
-rw-r--r--contrib/python/pip/pip/_internal/vcs/bazaar.py34
-rw-r--r--contrib/python/pip/pip/_internal/vcs/git.py81
-rw-r--r--contrib/python/pip/pip/_internal/vcs/mercurial.py45
-rw-r--r--contrib/python/pip/pip/_internal/vcs/subversion.py43
-rw-r--r--contrib/python/pip/pip/_internal/vcs/versioncontrol.py107
-rw-r--r--contrib/python/pip/pip/_internal/wheel_builder.py26
-rw-r--r--contrib/python/pip/pip/_vendor/cachecontrol/__init__.py2
-rw-r--r--contrib/python/pip/pip/_vendor/certifi/__init__.py2
-rw-r--r--contrib/python/pip/pip/_vendor/certifi/cacert.pem323
-rw-r--r--contrib/python/pip/pip/_vendor/certifi/core.py33
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/__init__.py4
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/database.py1329
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/index.py508
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/locators.py1295
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/manifest.py384
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/markers.py162
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/metadata.py1031
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/scripts.py2
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/version.py750
-rw-r--r--contrib/python/pip/pip/_vendor/distlib/wheel.py1100
-rw-r--r--contrib/python/pip/pip/_vendor/msgpack/__init__.py4
-rw-r--r--contrib/python/pip/pip/_vendor/pkg_resources/__init__.py2
-rw-r--r--contrib/python/pip/pip/_vendor/platformdirs/version.py4
-rw-r--r--contrib/python/pip/pip/_vendor/pygments/__init__.py2
-rw-r--r--contrib/python/pip/pip/_vendor/requests/__version__.py4
-rw-r--r--contrib/python/pip/pip/_vendor/requests/compat.py12
-rw-r--r--contrib/python/pip/pip/_vendor/requests/models.py4
-rw-r--r--contrib/python/pip/pip/_vendor/requests/utils.py22
-rw-r--r--contrib/python/pip/pip/_vendor/resolvelib/__init__.py6
-rw-r--r--contrib/python/pip/pip/_vendor/resolvelib/reporters.py2
-rw-r--r--contrib/python/pip/pip/_vendor/resolvelib/resolvers/__init__.py8
-rw-r--r--contrib/python/pip/pip/_vendor/resolvelib/resolvers/resolution.py101
-rw-r--r--contrib/python/pip/pip/_vendor/rich/__main__.py52
-rw-r--r--contrib/python/pip/pip/_vendor/rich/_inspect.py2
-rw-r--r--contrib/python/pip/pip/_vendor/rich/_ratio.py8
-rw-r--r--contrib/python/pip/pip/_vendor/rich/align.py8
-rw-r--r--contrib/python/pip/pip/_vendor/rich/box.py8
-rw-r--r--contrib/python/pip/pip/_vendor/rich/console.py45
-rw-r--r--contrib/python/pip/pip/_vendor/rich/control.py8
-rw-r--r--contrib/python/pip/pip/_vendor/rich/diagnose.py1
-rw-r--r--contrib/python/pip/pip/_vendor/rich/emoji.py7
-rw-r--r--contrib/python/pip/pip/_vendor/rich/live.py39
-rw-r--r--contrib/python/pip/pip/_vendor/rich/live_render.py8
-rw-r--r--contrib/python/pip/pip/_vendor/rich/logging.py2
-rw-r--r--contrib/python/pip/pip/_vendor/rich/panel.py7
-rw-r--r--contrib/python/pip/pip/_vendor/rich/progress.py30
-rw-r--r--contrib/python/pip/pip/_vendor/rich/spinner.py20
-rw-r--r--contrib/python/pip/pip/_vendor/rich/syntax.py29
-rw-r--r--contrib/python/pip/pip/_vendor/rich/traceback.py49
-rw-r--r--contrib/python/pip/pip/_vendor/truststore/_api.py2
-rw-r--r--contrib/python/pip/pip/_vendor/typing_extensions.py4584
-rw-r--r--contrib/python/pip/pip/_vendor/vendor.txt19
-rw-r--r--contrib/python/pip/ya.make11
182 files changed, 2893 insertions, 13367 deletions
diff --git a/contrib/python/pip/.dist-info/METADATA b/contrib/python/pip/.dist-info/METADATA
index 3cae064c16e..2cf904bc205 100644
--- a/contrib/python/pip/.dist-info/METADATA
+++ b/contrib/python/pip/.dist-info/METADATA
@@ -1,16 +1,15 @@
Metadata-Version: 2.4
Name: pip
-Version: 25.1.1
+Version: 25.2
Summary: The PyPA recommended tool for installing Python packages.
Author-email: The pip developers <[email protected]>
-License: MIT
+License-Expression: MIT
Project-URL: Homepage, https://pip.pypa.io/
Project-URL: Documentation, https://pip.pypa.io
Project-URL: Source, https://github.com/pypa/pip
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Build Tools
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
@@ -20,12 +19,35 @@ Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.9
Description-Content-Type: text/x-rst
-License-File: LICENSE.txt
License-File: AUTHORS.txt
+License-File: LICENSE.txt
+License-File: src/pip/_vendor/msgpack/COPYING
+License-File: src/pip/_vendor/cachecontrol/LICENSE.txt
+License-File: src/pip/_vendor/certifi/LICENSE
+License-File: src/pip/_vendor/dependency_groups/LICENSE.txt
+License-File: src/pip/_vendor/distlib/LICENSE.txt
+License-File: src/pip/_vendor/distro/LICENSE
+License-File: src/pip/_vendor/idna/LICENSE.md
+License-File: src/pip/_vendor/packaging/LICENSE
+License-File: src/pip/_vendor/packaging/LICENSE.APACHE
+License-File: src/pip/_vendor/packaging/LICENSE.BSD
+License-File: src/pip/_vendor/pkg_resources/LICENSE
+License-File: src/pip/_vendor/platformdirs/LICENSE
+License-File: src/pip/_vendor/pygments/LICENSE
+License-File: src/pip/_vendor/pyproject_hooks/LICENSE
+License-File: src/pip/_vendor/requests/LICENSE
+License-File: src/pip/_vendor/resolvelib/LICENSE
+License-File: src/pip/_vendor/rich/LICENSE
+License-File: src/pip/_vendor/tomli/LICENSE
+License-File: src/pip/_vendor/tomli/LICENSE-HEADER
+License-File: src/pip/_vendor/tomli_w/LICENSE
+License-File: src/pip/_vendor/truststore/LICENSE
+License-File: src/pip/_vendor/urllib3/LICENSE.txt
Dynamic: license-file
pip - The Python Package Installer
diff --git a/contrib/python/pip/AUTHORS.txt b/contrib/python/pip/AUTHORS.txt
index 08441a0a294..c8a7c68fa7a 100644
--- a/contrib/python/pip/AUTHORS.txt
+++ b/contrib/python/pip/AUTHORS.txt
@@ -220,11 +220,13 @@ Davidovich
ddelange
Deepak Sharma
Deepyaman Datta
+Denis Roussel (ACSONE)
Denise Yu
dependabot[bot]
derwolfe
Desetude
developer
+Devesh Kumar
Devesh Kumar Singh
devsagul
Diego Caraballo
@@ -295,6 +297,7 @@ Gabriel de Perthuis
Garry Polley
gavin
gdanielson
+Gene Wood
Geoffrey Sneddon
George Margaritis
George Song
@@ -333,6 +336,7 @@ Hugo Lopes Tavares
Hugo van Kemenade
Hugues Bruant
Hynek Schlawack
+iamsrp-deshaw
Ian Bicking
Ian Cordasco
Ian Lee
@@ -640,6 +644,7 @@ Pulkit Goyal
q0w
Qiangning Hong
Qiming Xu
+qraqras
Quentin Lee
Quentin Pradet
R. David Murray
@@ -665,6 +670,7 @@ Robert McGibbon
Robert Pollak
Robert T. McGibbon
robin elisha robinson
+Rodney, Tiara
Roey Berman
Rohan Jain
Roman Bogorodskiy
@@ -680,6 +686,7 @@ Russell Keith-Magee
Ryan Shepherd
Ryan Wooden
ryneeverett
+Ryuma Asai
S. Guliaev
Sachi King
Salvatore Rinchiera
@@ -694,6 +701,8 @@ Sebastian Jordan
Sebastian Schaetz
Segev Finer
SeongSoo Cho
+Sepehr Rasouli
+sepehrrasooli
Sergey Vasilyev
Seth Michael Larson
Seth Woodworth
@@ -719,6 +728,7 @@ Stavros Korokithakis
Stefan Scherfke
Stefano Rivera
Stephan Erb
+Stephen Payne
Stephen Rosen
stepshal
Steve (Gadget) Barnes
@@ -811,7 +821,9 @@ Yeray Diaz Diaz
Yoval P
Yu Jian
Yuan Jing Vincent Yan
+Yuki Kobayashi
Yusuke Hayashi
+zackzack38
Zearin
Zhiping Deng
ziebam
diff --git a/contrib/python/pip/COPYING b/contrib/python/pip/COPYING
new file mode 100644
index 00000000000..f067af3aae1
--- /dev/null
+++ b/contrib/python/pip/COPYING
@@ -0,0 +1,14 @@
+Copyright (C) 2008-2011 INADA Naoki <[email protected]>
+
+ 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.
+
diff --git a/contrib/python/pip/LICENSE b/contrib/python/pip/LICENSE
new file mode 100644
index 00000000000..7ec568c1136
--- /dev/null
+++ b/contrib/python/pip/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2022 Seth Michael Larson
+
+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.
diff --git a/contrib/python/pip/LICENSE-HEADER b/contrib/python/pip/LICENSE-HEADER
new file mode 100644
index 00000000000..aba78dd2cfc
--- /dev/null
+++ b/contrib/python/pip/LICENSE-HEADER
@@ -0,0 +1,3 @@
+SPDX-License-Identifier: MIT
+SPDX-FileCopyrightText: 2021 Taneli Hukkinen
+Licensed to PSF under a Contributor Agreement.
diff --git a/contrib/python/pip/LICENSE.APACHE b/contrib/python/pip/LICENSE.APACHE
new file mode 100644
index 00000000000..f433b1a53f5
--- /dev/null
+++ b/contrib/python/pip/LICENSE.APACHE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/contrib/python/pip/LICENSE.BSD b/contrib/python/pip/LICENSE.BSD
new file mode 100644
index 00000000000..42ce7b75c92
--- /dev/null
+++ b/contrib/python/pip/LICENSE.BSD
@@ -0,0 +1,23 @@
+Copyright (c) Donald Stufft and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/contrib/python/pip/LICENSE.md b/contrib/python/pip/LICENSE.md
new file mode 100644
index 00000000000..19b6b45242c
--- /dev/null
+++ b/contrib/python/pip/LICENSE.md
@@ -0,0 +1,31 @@
+BSD 3-Clause License
+
+Copyright (c) 2013-2024, Kim Davies and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/contrib/python/pip/pip/__init__.py b/contrib/python/pip/pip/__init__.py
index d4b19f02dea..c81f38189c9 100644
--- a/contrib/python/pip/pip/__init__.py
+++ b/contrib/python/pip/pip/__init__.py
@@ -1,9 +1,9 @@
-from typing import List, Optional
+from __future__ import annotations
-__version__ = "25.1.1"
+__version__ = "25.2"
-def main(args: Optional[List[str]] = None) -> int:
+def main(args: list[str] | None = None) -> int:
"""This is an internal API only meant for use by pip's own console scripts.
For additional details, see https://github.com/pypa/pip/issues/7498.
diff --git a/contrib/python/pip/pip/_internal/__init__.py b/contrib/python/pip/pip/_internal/__init__.py
index 1a5b7f87f97..24d0baf0c31 100644
--- a/contrib/python/pip/pip/_internal/__init__.py
+++ b/contrib/python/pip/pip/_internal/__init__.py
@@ -1,4 +1,4 @@
-from typing import List, Optional
+from __future__ import annotations
from pip._internal.utils import _log
@@ -7,7 +7,7 @@ from pip._internal.utils import _log
_log.init_logging()
-def main(args: Optional[List[str]] = None) -> int:
+def main(args: list[str] | None = None) -> int:
"""This is preserved for old console scripts that may still be referencing
it.
diff --git a/contrib/python/pip/pip/_internal/build_env.py b/contrib/python/pip/pip/_internal/build_env.py
index 22c7476702b..3a246a1e349 100644
--- a/contrib/python/pip/pip/_internal/build_env.py
+++ b/contrib/python/pip/pip/_internal/build_env.py
@@ -1,5 +1,7 @@
"""Build Environment used for isolation during sdist building"""
+from __future__ import annotations
+
import logging
import os
import pathlib
@@ -7,8 +9,9 @@ import site
import sys
import textwrap
from collections import OrderedDict
+from collections.abc import Iterable
from types import TracebackType
-from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
+from typing import TYPE_CHECKING, Protocol
from pip._vendor.packaging.version import Version
@@ -23,11 +26,12 @@ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
if TYPE_CHECKING:
from pip._internal.index.package_finder import PackageFinder
+ from pip._internal.req.req_install import InstallRequirement
logger = logging.getLogger(__name__)
-def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
+def _dedup(a: str, b: str) -> tuple[str] | tuple[str, str]:
return (a, b) if a != b else (a,)
@@ -56,7 +60,7 @@ def get_runnable_pip() -> str:
return os.fsdecode(source / "__pip-runner__.py")
-def _get_system_sitepackages() -> Set[str]:
+def _get_system_sitepackages() -> set[str]:
"""Get system site packages
Usually from site.getsitepackages,
@@ -76,10 +80,108 @@ def _get_system_sitepackages() -> Set[str]:
return {os.path.normcase(path) for path in system_sites}
+class BuildEnvironmentInstaller(Protocol):
+ """
+ Interface for installing build dependencies into an isolated build
+ environment.
+ """
+
+ def install(
+ self,
+ requirements: Iterable[str],
+ prefix: _Prefix,
+ *,
+ kind: str,
+ for_req: InstallRequirement | None,
+ ) -> None: ...
+
+
+class SubprocessBuildEnvironmentInstaller:
+ """
+ Install build dependencies by calling pip in a subprocess.
+ """
+
+ def __init__(self, finder: PackageFinder) -> None:
+ self.finder = finder
+
+ def install(
+ self,
+ requirements: Iterable[str],
+ prefix: _Prefix,
+ *,
+ kind: str,
+ for_req: InstallRequirement | None,
+ ) -> None:
+ finder = self.finder
+ args: list[str] = [
+ sys.executable,
+ get_runnable_pip(),
+ "install",
+ "--ignore-installed",
+ "--no-user",
+ "--prefix",
+ prefix.path,
+ "--no-warn-script-location",
+ "--disable-pip-version-check",
+ # As the build environment is ephemeral, it's wasteful to
+ # pre-compile everything, especially as not every Python
+ # module will be used/compiled in most cases.
+ "--no-compile",
+ # The prefix specified two lines above, thus
+ # target from config file or env var should be ignored
+ "--target",
+ "",
+ ]
+ if logger.getEffectiveLevel() <= logging.DEBUG:
+ args.append("-vv")
+ elif logger.getEffectiveLevel() <= VERBOSE:
+ args.append("-v")
+ for format_control in ("no_binary", "only_binary"):
+ formats = getattr(finder.format_control, format_control)
+ args.extend(
+ (
+ "--" + format_control.replace("_", "-"),
+ ",".join(sorted(formats or {":none:"})),
+ )
+ )
+
+ index_urls = finder.index_urls
+ if index_urls:
+ args.extend(["-i", index_urls[0]])
+ for extra_index in index_urls[1:]:
+ args.extend(["--extra-index-url", extra_index])
+ else:
+ args.append("--no-index")
+ for link in finder.find_links:
+ args.extend(["--find-links", link])
+
+ if finder.proxy:
+ args.extend(["--proxy", finder.proxy])
+ for host in finder.trusted_hosts:
+ args.extend(["--trusted-host", host])
+ if finder.custom_cert:
+ args.extend(["--cert", finder.custom_cert])
+ if finder.client_cert:
+ args.extend(["--client-cert", finder.client_cert])
+ if finder.allow_all_prereleases:
+ args.append("--pre")
+ if finder.prefer_binary:
+ args.append("--prefer-binary")
+ args.append("--")
+ args.extend(requirements)
+ with open_spinner(f"Installing {kind}") as spinner:
+ call_subprocess(
+ args,
+ command_desc=f"pip subprocess to install {kind}",
+ spinner=spinner,
+ )
+
+
class BuildEnvironment:
"""Creates and manages an isolated environment to install build deps"""
- def __init__(self) -> None:
+ def __init__(self, installer: BuildEnvironmentInstaller) -> None:
+ self.installer = installer
temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
self._prefixes = OrderedDict(
@@ -87,8 +189,8 @@ class BuildEnvironment:
for name in ("normal", "overlay")
)
- self._bin_dirs: List[str] = []
- self._lib_dirs: List[str] = []
+ self._bin_dirs: list[str] = []
+ self._lib_dirs: list[str] = []
for prefix in reversed(list(self._prefixes.values())):
self._bin_dirs.append(prefix.bin_dir)
self._lib_dirs.extend(prefix.lib_dirs)
@@ -156,9 +258,9 @@ class BuildEnvironment:
def __exit__(
self,
- exc_type: Optional[Type[BaseException]],
- exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType],
+ exc_type: type[BaseException] | None,
+ exc_val: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
for varname, old_value in self._save_env.items():
if old_value is None:
@@ -168,7 +270,7 @@ class BuildEnvironment:
def check_requirements(
self, reqs: Iterable[str]
- ) -> Tuple[Set[Tuple[str, str]], Set[str]]:
+ ) -> tuple[set[tuple[str, str]], set[str]]:
"""Return 2 sets:
- conflicting requirements: set of (installed, wanted) reqs tuples
- missing requirements: set of reqs
@@ -202,96 +304,18 @@ class BuildEnvironment:
def install_requirements(
self,
- finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
*,
kind: str,
+ for_req: InstallRequirement | None = None,
) -> None:
prefix = self._prefixes[prefix_as_string]
assert not prefix.setup
prefix.setup = True
if not requirements:
return
- self._install_requirements(
- get_runnable_pip(),
- finder,
- requirements,
- prefix,
- kind=kind,
- )
-
- @staticmethod
- def _install_requirements(
- pip_runnable: str,
- finder: "PackageFinder",
- requirements: Iterable[str],
- prefix: _Prefix,
- *,
- kind: str,
- ) -> None:
- args: List[str] = [
- sys.executable,
- pip_runnable,
- "install",
- "--ignore-installed",
- "--no-user",
- "--prefix",
- prefix.path,
- "--no-warn-script-location",
- "--disable-pip-version-check",
- # As the build environment is ephemeral, it's wasteful to
- # pre-compile everything, especially as not every Python
- # module will be used/compiled in most cases.
- "--no-compile",
- # The prefix specified two lines above, thus
- # target from config file or env var should be ignored
- "--target",
- "",
- ]
- if logger.getEffectiveLevel() <= logging.DEBUG:
- args.append("-vv")
- elif logger.getEffectiveLevel() <= VERBOSE:
- args.append("-v")
- for format_control in ("no_binary", "only_binary"):
- formats = getattr(finder.format_control, format_control)
- args.extend(
- (
- "--" + format_control.replace("_", "-"),
- ",".join(sorted(formats or {":none:"})),
- )
- )
-
- index_urls = finder.index_urls
- if index_urls:
- args.extend(["-i", index_urls[0]])
- for extra_index in index_urls[1:]:
- args.extend(["--extra-index-url", extra_index])
- else:
- args.append("--no-index")
- for link in finder.find_links:
- args.extend(["--find-links", link])
-
- if finder.proxy:
- args.extend(["--proxy", finder.proxy])
- for host in finder.trusted_hosts:
- args.extend(["--trusted-host", host])
- if finder.custom_cert:
- args.extend(["--cert", finder.custom_cert])
- if finder.client_cert:
- args.extend(["--client-cert", finder.client_cert])
- if finder.allow_all_prereleases:
- args.append("--pre")
- if finder.prefer_binary:
- args.append("--prefer-binary")
- args.append("--")
- args.extend(requirements)
- with open_spinner(f"Installing {kind}") as spinner:
- call_subprocess(
- args,
- command_desc=f"pip subprocess to install {kind}",
- spinner=spinner,
- )
+ self.installer.install(requirements, prefix, kind=kind, for_req=for_req)
class NoOpBuildEnvironment(BuildEnvironment):
@@ -305,9 +329,9 @@ class NoOpBuildEnvironment(BuildEnvironment):
def __exit__(
self,
- exc_type: Optional[Type[BaseException]],
- exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType],
+ exc_type: type[BaseException] | None,
+ exc_val: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
pass
@@ -316,10 +340,10 @@ class NoOpBuildEnvironment(BuildEnvironment):
def install_requirements(
self,
- finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
*,
kind: str,
+ for_req: InstallRequirement | None = None,
) -> None:
raise NotImplementedError()
diff --git a/contrib/python/pip/pip/_internal/cache.py b/contrib/python/pip/pip/_internal/cache.py
index 97d917193d3..ce98f288395 100644
--- a/contrib/python/pip/pip/_internal/cache.py
+++ b/contrib/python/pip/pip/_internal/cache.py
@@ -1,11 +1,13 @@
"""Cache Management"""
+from __future__ import annotations
+
import hashlib
import json
import logging
import os
from pathlib import Path
-from typing import Any, Dict, List, Optional
+from typing import Any
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
from pip._vendor.packaging.utils import canonicalize_name
@@ -22,7 +24,7 @@ logger = logging.getLogger(__name__)
ORIGIN_JSON_NAME = "origin.json"
-def _hash_dict(d: Dict[str, str]) -> str:
+def _hash_dict(d: dict[str, str]) -> str:
"""Return a stable sha224 of a dictionary."""
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return hashlib.sha224(s.encode("ascii")).hexdigest()
@@ -39,7 +41,7 @@ class Cache:
assert not cache_dir or os.path.isabs(cache_dir)
self.cache_dir = cache_dir or None
- def _get_cache_path_parts(self, link: Link) -> List[str]:
+ def _get_cache_path_parts(self, link: Link) -> list[str]:
"""Get parts of part that must be os.path.joined with cache_dir"""
# We want to generate an url to use as our cache key, we don't want to
@@ -72,7 +74,7 @@ class Cache:
return parts
- def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
+ def _get_candidates(self, link: Link, canonical_package_name: str) -> list[Any]:
can_not_cache = not self.cache_dir or not canonical_package_name or not link
if can_not_cache:
return []
@@ -89,8 +91,8 @@ class Cache:
def get(
self,
link: Link,
- package_name: Optional[str],
- supported_tags: List[Tag],
+ package_name: str | None,
+ supported_tags: list[Tag],
) -> Link:
"""Returns a link to a cached item if it exists, otherwise returns the
passed link.
@@ -127,8 +129,8 @@ class SimpleWheelCache(Cache):
def get(
self,
link: Link,
- package_name: Optional[str],
- supported_tags: List[Tag],
+ package_name: str | None,
+ supported_tags: list[Tag],
) -> Link:
candidates = []
@@ -188,7 +190,7 @@ class CacheEntry:
):
self.link = link
self.persistent = persistent
- self.origin: Optional[DirectUrl] = None
+ self.origin: DirectUrl | None = None
origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
if origin_direct_url_path.exists():
try:
@@ -225,8 +227,8 @@ class WheelCache(Cache):
def get(
self,
link: Link,
- package_name: Optional[str],
- supported_tags: List[Tag],
+ package_name: str | None,
+ supported_tags: list[Tag],
) -> Link:
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
if cache_entry is None:
@@ -236,9 +238,9 @@ class WheelCache(Cache):
def get_cache_entry(
self,
link: Link,
- package_name: Optional[str],
- supported_tags: List[Tag],
- ) -> Optional[CacheEntry]:
+ package_name: str | None,
+ supported_tags: list[Tag],
+ ) -> CacheEntry | None:
"""Returns a CacheEntry with a link to a cached item if it exists or
None. The cache entry indicates if the item was found in the persistent
or ephemeral cache.
diff --git a/contrib/python/pip/pip/_internal/cli/autocompletion.py b/contrib/python/pip/pip/_internal/cli/autocompletion.py
index 4fa461293c7..f22cd1159a2 100644
--- a/contrib/python/pip/pip/_internal/cli/autocompletion.py
+++ b/contrib/python/pip/pip/_internal/cli/autocompletion.py
@@ -1,10 +1,13 @@
"""Logic that powers autocompletion installed by ``pip completion``."""
+from __future__ import annotations
+
import optparse
import os
import sys
+from collections.abc import Iterable
from itertools import chain
-from typing import Any, Iterable, List, Optional
+from typing import Any
from pip._internal.cli.main_parser import create_main_parser
from pip._internal.commands import commands_dict, create_command
@@ -32,7 +35,7 @@ def autocomplete() -> None:
options = []
# subcommand
- subcommand_name: Optional[str] = None
+ subcommand_name: str | None = None
for word in cwords:
if word in subcommands:
subcommand_name = word
@@ -100,6 +103,12 @@ def autocomplete() -> None:
if option[1] and option[0][:2] == "--":
opt_label += "="
print(opt_label)
+
+ # Complete sub-commands (unless one is already given).
+ if not any(name in cwords for name in subcommand.handler_map()):
+ for handler_name in subcommand.handler_map():
+ if handler_name.startswith(current):
+ print(handler_name)
else:
# show main parser options only when necessary
@@ -121,8 +130,8 @@ def autocomplete() -> None:
def get_path_completion_type(
- cwords: List[str], cword: int, opts: Iterable[Any]
-) -> Optional[str]:
+ cwords: list[str], cword: int, opts: Iterable[Any]
+) -> str | None:
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
:param cwords: same as the environmental variable ``COMP_WORDS``
diff --git a/contrib/python/pip/pip/_internal/cli/base_command.py b/contrib/python/pip/pip/_internal/cli/base_command.py
index 1d71d67e7a4..7acc29cb349 100644
--- a/contrib/python/pip/pip/_internal/cli/base_command.py
+++ b/contrib/python/pip/pip/_internal/cli/base_command.py
@@ -1,5 +1,7 @@
"""Base Command class, and related routines"""
+from __future__ import annotations
+
import logging
import logging.config
import optparse
@@ -7,7 +9,7 @@ import os
import sys
import traceback
from optparse import Values
-from typing import List, Optional, Tuple
+from typing import Callable
from pip._vendor.rich import reconfigure
from pip._vendor.rich import traceback as rich_traceback
@@ -60,7 +62,7 @@ class Command(CommandContextMixIn):
isolated=isolated,
)
- self.tempdir_registry: Optional[TempDirRegistry] = None
+ self.tempdir_registry: TempDirRegistry | None = None
# Commands should add options to this option group
optgroup_name = f"{self.name.capitalize()} Options"
@@ -87,10 +89,10 @@ class Command(CommandContextMixIn):
# are present.
assert not hasattr(options, "no_index")
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
raise NotImplementedError
- def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int:
+ def _run_wrapper(self, level_number: int, options: Values, args: list[str]) -> int:
def _inner_run() -> int:
try:
return self.run(options, args)
@@ -147,18 +149,18 @@ class Command(CommandContextMixIn):
return UNKNOWN_ERROR
- def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]:
+ def parse_args(self, args: list[str]) -> tuple[Values, list[str]]:
# factored out for testability
return self.parser.parse_args(args)
- def main(self, args: List[str]) -> int:
+ def main(self, args: list[str]) -> int:
try:
with self.main_context():
return self._main(args)
finally:
logging.shutdown()
- def _main(self, args: List[str]) -> int:
+ def _main(self, args: list[str]) -> int:
# We must initialize this before the tempdir manager, otherwise the
# configuration would not be accessible by the time we clean up the
# tempdir manager.
@@ -174,6 +176,9 @@ class Command(CommandContextMixIn):
if options.debug_mode:
self.verbosity = 2
+ if hasattr(options, "progress_bar") and options.progress_bar == "auto":
+ options.progress_bar = "on" if self.verbosity >= 0 else "off"
+
reconfigure(no_color=options.no_color)
level_number = setup_logging(
verbosity=self.verbosity,
@@ -231,3 +236,9 @@ class Command(CommandContextMixIn):
options.cache_dir = None
return self._run_wrapper(level_number, options, args)
+
+ def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
+ """
+ map of names to handler actions for commands with sub-actions
+ """
+ return {}
diff --git a/contrib/python/pip/pip/_internal/cli/cmdoptions.py b/contrib/python/pip/pip/_internal/cli/cmdoptions.py
index 88392836cd9..3519dadf13d 100644
--- a/contrib/python/pip/pip/_internal/cli/cmdoptions.py
+++ b/contrib/python/pip/pip/_internal/cli/cmdoptions.py
@@ -9,6 +9,7 @@ pass on state. To be consistent, all options will follow this design.
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
+from __future__ import annotations
import importlib.util
import logging
@@ -18,7 +19,7 @@ import textwrap
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
from textwrap import dedent
-from typing import Any, Callable, Dict, Optional, Tuple
+from typing import Any, Callable
from pip._vendor.packaging.utils import canonicalize_name
@@ -48,7 +49,7 @@ def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
parser.error(msg)
-def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
+def make_option_group(group: dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
"""
Return an OptionGroup object
group -- assumed to be dict with 'name' and 'options' keys
@@ -227,9 +228,13 @@ progress_bar: Callable[..., Option] = partial(
"--progress-bar",
dest="progress_bar",
type="choice",
- choices=["on", "off", "raw"],
- default="on",
- help="Specify whether the progress bar should be used [on, off, raw] (default: on)",
+ choices=["auto", "on", "off", "raw"],
+ default="auto",
+ help=(
+ "Specify whether the progress bar should be used. In 'auto'"
+ " mode, --quiet will suppress all progress bars."
+ " [auto, on, off, raw] (default: auto)"
+ ),
)
log: Callable[..., Option] = partial(
@@ -289,7 +294,7 @@ resume_retries: Callable[..., Option] = partial(
"--resume-retries",
dest="resume_retries",
type="int",
- default=0,
+ default=5,
help="Maximum attempts to resume or restart an incomplete download. "
"(default: %default)",
)
@@ -555,7 +560,7 @@ platforms: Callable[..., Option] = partial(
# This was made a separate function for unit-testing purposes.
-def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]:
+def _convert_python_version(value: str) -> tuple[tuple[int, ...], str | None]:
"""
Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
@@ -1090,7 +1095,7 @@ use_deprecated_feature: Callable[..., Option] = partial(
# groups #
##########
-general_group: Dict[str, Any] = {
+general_group: dict[str, Any] = {
"name": "General Options",
"options": [
help_,
@@ -1122,7 +1127,7 @@ general_group: Dict[str, Any] = {
],
}
-index_group: Dict[str, Any] = {
+index_group: dict[str, Any] = {
"name": "Package Index Options",
"options": [
index_url,
diff --git a/contrib/python/pip/pip/_internal/cli/command_context.py b/contrib/python/pip/pip/_internal/cli/command_context.py
index 139995ac3f1..9c167bdc339 100644
--- a/contrib/python/pip/pip/_internal/cli/command_context.py
+++ b/contrib/python/pip/pip/_internal/cli/command_context.py
@@ -1,5 +1,6 @@
-from contextlib import ExitStack, contextmanager
-from typing import ContextManager, Generator, TypeVar
+from collections.abc import Generator
+from contextlib import AbstractContextManager, ExitStack, contextmanager
+from typing import TypeVar
_T = TypeVar("_T", covariant=True)
@@ -21,7 +22,7 @@ class CommandContextMixIn:
finally:
self._in_main_context = False
- def enter_context(self, context_provider: ContextManager[_T]) -> _T:
+ def enter_context(self, context_provider: AbstractContextManager[_T]) -> _T:
assert self._in_main_context
return self._main_context.enter_context(context_provider)
diff --git a/contrib/python/pip/pip/_internal/cli/index_command.py b/contrib/python/pip/pip/_internal/cli/index_command.py
index 87a1e088564..f6a82c8a80c 100644
--- a/contrib/python/pip/pip/_internal/cli/index_command.py
+++ b/contrib/python/pip/pip/_internal/cli/index_command.py
@@ -6,12 +6,14 @@ so commands which don't always hit the network (e.g. list w/o --outdated or
--uptodate) don't need waste time importing PipSession and friends.
"""
+from __future__ import annotations
+
import logging
import os
import sys
from functools import lru_cache
from optparse import Values
-from typing import TYPE_CHECKING, List, Optional
+from typing import TYPE_CHECKING
from pip._vendor import certifi
@@ -27,7 +29,7 @@ logger = logging.getLogger(__name__)
@lru_cache
-def _create_truststore_ssl_context() -> Optional["SSLContext"]:
+def _create_truststore_ssl_context() -> SSLContext | None:
if sys.version_info < (3, 10):
logger.debug("Disabling truststore because Python version isn't 3.10+")
return None
@@ -56,10 +58,10 @@ class SessionCommandMixin(CommandContextMixIn):
def __init__(self) -> None:
super().__init__()
- self._session: Optional[PipSession] = None
+ self._session: PipSession | None = None
@classmethod
- def _get_index_urls(cls, options: Values) -> Optional[List[str]]:
+ def _get_index_urls(cls, options: Values) -> list[str] | None:
"""Return a list of index urls from user-provided options."""
index_urls = []
if not getattr(options, "no_index", False):
@@ -72,7 +74,7 @@ class SessionCommandMixin(CommandContextMixIn):
# Return None rather than an empty list
return index_urls or None
- def get_default_session(self, options: Values) -> "PipSession":
+ def get_default_session(self, options: Values) -> PipSession:
"""Get a default-managed session."""
if self._session is None:
self._session = self.enter_context(self._build_session(options))
@@ -85,9 +87,9 @@ class SessionCommandMixin(CommandContextMixIn):
def _build_session(
self,
options: Values,
- retries: Optional[int] = None,
- timeout: Optional[int] = None,
- ) -> "PipSession":
+ retries: int | None = None,
+ timeout: int | None = None,
+ ) -> PipSession:
from pip._internal.network.session import PipSession
cache_dir = options.cache_dir
@@ -134,7 +136,7 @@ class SessionCommandMixin(CommandContextMixIn):
return session
-def _pip_self_version_check(session: "PipSession", options: Values) -> None:
+def _pip_self_version_check(session: PipSession, options: Values) -> None:
from pip._internal.self_outdated_check import pip_self_version_check as check
check(session, options)
diff --git a/contrib/python/pip/pip/_internal/cli/main.py b/contrib/python/pip/pip/_internal/cli/main.py
index 377476c18c3..9a161fd1720 100644
--- a/contrib/python/pip/pip/_internal/cli/main.py
+++ b/contrib/python/pip/pip/_internal/cli/main.py
@@ -1,11 +1,12 @@
"""Primary application entrypoint."""
+from __future__ import annotations
+
import locale
import logging
import os
import sys
import warnings
-from typing import List, Optional
from pip._internal.cli.autocompletion import autocomplete
from pip._internal.cli.main_parser import parse_command
@@ -43,7 +44,7 @@ logger = logging.getLogger(__name__)
# main, this should not be an issue in practice.
-def main(args: Optional[List[str]] = None) -> int:
+def main(args: list[str] | None = None) -> int:
if args is None:
args = sys.argv[1:]
diff --git a/contrib/python/pip/pip/_internal/cli/main_parser.py b/contrib/python/pip/pip/_internal/cli/main_parser.py
index c52684a81fe..5ce9f5a02d4 100644
--- a/contrib/python/pip/pip/_internal/cli/main_parser.py
+++ b/contrib/python/pip/pip/_internal/cli/main_parser.py
@@ -1,9 +1,10 @@
"""A single place for constructing and exposing the main parser"""
+from __future__ import annotations
+
import os
import subprocess
import sys
-from typing import List, Optional, Tuple
from pip._internal.build_env import get_runnable_pip
from pip._internal.cli import cmdoptions
@@ -46,7 +47,7 @@ def create_main_parser() -> ConfigOptionParser:
return parser
-def identify_python_interpreter(python: str) -> Optional[str]:
+def identify_python_interpreter(python: str) -> str | None:
# If the named file exists, use it.
# If it's a directory, assume it's a virtual environment and
# look for the environment's Python executable.
@@ -65,7 +66,7 @@ def identify_python_interpreter(python: str) -> Optional[str]:
return None
-def parse_command(args: List[str]) -> Tuple[str, List[str]]:
+def parse_command(args: list[str]) -> tuple[str, list[str]]:
parser = create_main_parser()
# Note: parser calls disable_interspersed_args(), so the result of this
diff --git a/contrib/python/pip/pip/_internal/cli/parser.py b/contrib/python/pip/pip/_internal/cli/parser.py
index bc4aca032d4..f8b8ac43bec 100644
--- a/contrib/python/pip/pip/_internal/cli/parser.py
+++ b/contrib/python/pip/pip/_internal/cli/parser.py
@@ -1,12 +1,15 @@
"""Base option parser setup"""
+from __future__ import annotations
+
import logging
import optparse
import shutil
import sys
import textwrap
+from collections.abc import Generator
from contextlib import suppress
-from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple
+from typing import Any, NoReturn
from pip._internal.cli.status_codes import UNKNOWN_ERROR
from pip._internal.configuration import Configuration, ConfigurationError
@@ -67,7 +70,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
return msg
- def format_description(self, description: Optional[str]) -> str:
+ def format_description(self, description: str | None) -> str:
# leave full control over description to us
if description:
if hasattr(self.parser, "main"):
@@ -85,7 +88,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
else:
return ""
- def format_epilog(self, epilog: Optional[str]) -> str:
+ def format_epilog(self, epilog: str | None) -> str:
# leave full control over epilog to us
if epilog:
return epilog
@@ -142,7 +145,7 @@ class CustomOptionParser(optparse.OptionParser):
return group
@property
- def option_list_all(self) -> List[optparse.Option]:
+ def option_list_all(self) -> list[optparse.Option]:
"""Get a list of all options, including those in option groups."""
res = self.option_list[:]
for i in self.option_groups:
@@ -177,33 +180,34 @@ class ConfigOptionParser(CustomOptionParser):
def _get_ordered_configuration_items(
self,
- ) -> Generator[Tuple[str, Any], None, None]:
+ ) -> Generator[tuple[str, Any], None, None]:
# Configuration gives keys in an unordered manner. Order them.
override_order = ["global", self.name, ":env:"]
# Pool the options into different groups
- section_items: Dict[str, List[Tuple[str, Any]]] = {
+ section_items: dict[str, list[tuple[str, Any]]] = {
name: [] for name in override_order
}
- for section_key, val in self.config.items():
- # ignore empty values
- if not val:
- logger.debug(
- "Ignoring configuration key '%s' as it's value is empty.",
- section_key,
- )
- continue
- section, key = section_key.split(".", 1)
- if section in override_order:
- section_items[section].append((key, val))
+ for _, value in self.config.items(): # noqa: PERF102
+ for section_key, val in value.items():
+ # ignore empty values
+ if not val:
+ logger.debug(
+ "Ignoring configuration key '%s' as its value is empty.",
+ section_key,
+ )
+ continue
+
+ section, key = section_key.split(".", 1)
+ if section in override_order:
+ section_items[section].append((key, val))
- # Yield each group in their override order
- for section in override_order:
- for key, val in section_items[section]:
- yield key, val
+ # Yield each group in their override order
+ for section in override_order:
+ yield from section_items[section]
- def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
+ def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]:
"""Updates the given defaults with values from the config files and
the environ. Does a little special handling for certain types of
options (lists)."""
diff --git a/contrib/python/pip/pip/_internal/cli/progress_bars.py b/contrib/python/pip/pip/_internal/cli/progress_bars.py
index ab9d76b252e..af1bb6a5e05 100644
--- a/contrib/python/pip/pip/_internal/cli/progress_bars.py
+++ b/contrib/python/pip/pip/_internal/cli/progress_bars.py
@@ -1,6 +1,9 @@
+from __future__ import annotations
+
import functools
import sys
-from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple, TypeVar
+from collections.abc import Generator, Iterable, Iterator
+from typing import Callable, Literal, TypeVar
from pip._vendor.rich.progress import (
BarColumn,
@@ -22,20 +25,21 @@ from pip._internal.utils.logging import get_console, get_indentation
T = TypeVar("T")
ProgressRenderer = Callable[[Iterable[T]], Iterator[T]]
+BarType = Literal["on", "off", "raw"]
def _rich_download_progress_bar(
iterable: Iterable[bytes],
*,
- bar_type: str,
- size: Optional[int],
- initial_progress: Optional[int] = None,
+ bar_type: BarType,
+ size: int | None,
+ initial_progress: int | None = None,
) -> Generator[bytes, None, None]:
assert bar_type == "on", "This should only be used in the default mode."
if not size:
total = float("inf")
- columns: Tuple[ProgressColumn, ...] = (
+ columns: tuple[ProgressColumn, ...] = (
TextColumn("[progress.description]{task.description}"),
SpinnerColumn("line", speed=1.5),
FileSizeColumn(),
@@ -49,18 +53,21 @@ def _rich_download_progress_bar(
BarColumn(),
DownloadColumn(),
TransferSpeedColumn(),
- TextColumn("eta"),
- TimeRemainingColumn(),
+ TextColumn("{task.fields[time_description]}"),
+ TimeRemainingColumn(elapsed_when_finished=True),
)
progress = Progress(*columns, refresh_per_second=5)
- task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
+ task_id = progress.add_task(
+ " " * (get_indentation() + 2), total=total, time_description="eta"
+ )
if initial_progress is not None:
progress.update(task_id, advance=initial_progress)
with progress:
for chunk in iterable:
yield chunk
progress.update(task_id, advance=len(chunk))
+ progress.update(task_id, time_description="")
def _rich_install_progress_bar(
@@ -88,8 +95,8 @@ def _rich_install_progress_bar(
def _raw_progress_bar(
iterable: Iterable[bytes],
*,
- size: Optional[int],
- initial_progress: Optional[int] = None,
+ size: int | None,
+ initial_progress: int | None = None,
) -> Generator[bytes, None, None]:
def write_progress(current: int, total: int) -> None:
sys.stdout.write(f"Progress {current} of {total}\n")
@@ -109,7 +116,7 @@ def _raw_progress_bar(
def get_download_progress_renderer(
- *, bar_type: str, size: Optional[int] = None, initial_progress: Optional[int] = None
+ *, bar_type: BarType, size: int | None = None, initial_progress: int | None = None
) -> ProgressRenderer[bytes]:
"""Get an object that can be used to render the download progress.
@@ -133,7 +140,7 @@ def get_download_progress_renderer(
def get_install_progress_renderer(
- *, bar_type: str, total: int
+ *, bar_type: BarType, total: int
) -> ProgressRenderer[InstallRequirement]:
"""Get an object that can be used to render the install progress.
Returns a callable, that takes an iterable to "wrap".
diff --git a/contrib/python/pip/pip/_internal/cli/req_command.py b/contrib/python/pip/pip/_internal/cli/req_command.py
index d9b51427055..dc1328ff019 100644
--- a/contrib/python/pip/pip/_internal/cli/req_command.py
+++ b/contrib/python/pip/pip/_internal/cli/req_command.py
@@ -5,11 +5,14 @@ need PackageFinder capability don't unnecessarily import the
PackageFinder machinery and all its vendored dependencies, etc.
"""
+from __future__ import annotations
+
import logging
from functools import partial
from optparse import Values
-from typing import Any, List, Optional, Tuple
+from typing import Any
+from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
from pip._internal.cli.index_command import IndexGroupCommand
@@ -58,8 +61,8 @@ def with_cleanup(func: Any) -> Any:
registry.set_delete(t, False)
def wrapper(
- self: RequirementCommand, options: Values, args: List[Any]
- ) -> Optional[int]:
+ self: RequirementCommand, options: Values, args: list[Any]
+ ) -> int | None:
assert self.tempdir_registry is not None
if options.no_clean:
configure_tempdir_registry(self.tempdir_registry)
@@ -100,7 +103,7 @@ class RequirementCommand(IndexGroupCommand):
session: PipSession,
finder: PackageFinder,
use_user_site: bool,
- download_dir: Optional[str] = None,
+ download_dir: str | None = None,
verbosity: int = 0,
) -> RequirementPreparer:
"""
@@ -134,6 +137,7 @@ class RequirementCommand(IndexGroupCommand):
src_dir=options.src_dir,
download_dir=download_dir,
build_isolation=options.build_isolation,
+ build_isolation_installer=SubprocessBuildEnvironmentInstaller(finder),
check_build_deps=options.check_build_deps,
build_tracker=build_tracker,
session=session,
@@ -153,14 +157,14 @@ class RequirementCommand(IndexGroupCommand):
preparer: RequirementPreparer,
finder: PackageFinder,
options: Values,
- wheel_cache: Optional[WheelCache] = None,
+ wheel_cache: WheelCache | None = None,
use_user_site: bool = False,
ignore_installed: bool = True,
ignore_requires_python: bool = False,
force_reinstall: bool = False,
upgrade_strategy: str = "to-satisfy-only",
- use_pep517: Optional[bool] = None,
- py_version_info: Optional[Tuple[int, ...]] = None,
+ use_pep517: bool | None = None,
+ py_version_info: tuple[int, ...] | None = None,
) -> BaseResolver:
"""
Create a Resolver instance for the given parameters.
@@ -208,15 +212,15 @@ class RequirementCommand(IndexGroupCommand):
def get_requirements(
self,
- args: List[str],
+ args: list[str],
options: Values,
finder: PackageFinder,
session: PipSession,
- ) -> List[InstallRequirement]:
+ ) -> list[InstallRequirement]:
"""
Parse command-line arguments into the corresponding requirements.
"""
- requirements: List[InstallRequirement] = []
+ requirements: list[InstallRequirement] = []
for filename in options.constraints:
for parsed_req in parse_requirements(
filename,
@@ -322,8 +326,8 @@ class RequirementCommand(IndexGroupCommand):
self,
options: Values,
session: PipSession,
- target_python: Optional[TargetPython] = None,
- ignore_requires_python: Optional[bool] = None,
+ target_python: TargetPython | None = None,
+ ignore_requires_python: bool | None = None,
) -> PackageFinder:
"""
Create a package finder appropriate to this requirement command.
diff --git a/contrib/python/pip/pip/_internal/cli/spinners.py b/contrib/python/pip/pip/_internal/cli/spinners.py
index cf2b976f377..58aad2853dd 100644
--- a/contrib/python/pip/pip/_internal/cli/spinners.py
+++ b/contrib/python/pip/pip/_internal/cli/spinners.py
@@ -1,15 +1,31 @@
+from __future__ import annotations
+
import contextlib
import itertools
import logging
import sys
import time
-from typing import IO, Generator, Optional
+from collections.abc import Generator
+from typing import IO, Final
+
+from pip._vendor.rich.console import (
+ Console,
+ ConsoleOptions,
+ RenderableType,
+ RenderResult,
+)
+from pip._vendor.rich.live import Live
+from pip._vendor.rich.measure import Measurement
+from pip._vendor.rich.text import Text
from pip._internal.utils.compat import WINDOWS
-from pip._internal.utils.logging import get_indentation
+from pip._internal.utils.logging import get_console, get_indentation
logger = logging.getLogger(__name__)
+SPINNER_CHARS: Final = r"-\|/"
+SPINS_PER_SECOND: Final = 8
+
class SpinnerInterface:
def spin(self) -> None:
@@ -23,10 +39,10 @@ class InteractiveSpinner(SpinnerInterface):
def __init__(
self,
message: str,
- file: Optional[IO[str]] = None,
- spin_chars: str = "-\\|/",
+ file: IO[str] | None = None,
+ spin_chars: str = SPINNER_CHARS,
# Empirically, 8 updates/second looks nice
- min_update_interval_seconds: float = 0.125,
+ min_update_interval_seconds: float = 1 / SPINS_PER_SECOND,
):
self._message = message
if file is None:
@@ -136,6 +152,66 @@ def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
spinner.finish("done")
+class _PipRichSpinner:
+ """
+ Custom rich spinner that matches the style of the legacy spinners.
+
+ (*) Updates will be handled in a background thread by a rich live panel
+ which will call render() automatically at the appropriate time.
+ """
+
+ def __init__(self, label: str) -> None:
+ self.label = label
+ self._spin_cycle = itertools.cycle(SPINNER_CHARS)
+ self._spinner_text = ""
+ self._finished = False
+ self._indent = get_indentation() * " "
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ yield self.render()
+
+ def __rich_measure__(
+ self, console: Console, options: ConsoleOptions
+ ) -> Measurement:
+ text = self.render()
+ return Measurement.get(console, options, text)
+
+ def render(self) -> RenderableType:
+ if not self._finished:
+ self._spinner_text = next(self._spin_cycle)
+
+ return Text.assemble(self._indent, self.label, " ... ", self._spinner_text)
+
+ def finish(self, status: str) -> None:
+ """Stop spinning and set a final status message."""
+ self._spinner_text = status
+ self._finished = True
+
+
+def open_rich_spinner(label: str, console: Console | None = None) -> Generator[None]:
+ if not logger.isEnabledFor(logging.INFO):
+ # Don't show spinner if --quiet is given.
+ yield
+ return
+
+ console = console or get_console()
+ spinner = _PipRichSpinner(label)
+ with Live(spinner, refresh_per_second=SPINS_PER_SECOND, console=console):
+ try:
+ yield
+ except KeyboardInterrupt:
+ spinner.finish("canceled")
+ raise
+ except Exception:
+ spinner.finish("error")
+ raise
+ else:
+ spinner.finish("done")
+
+
HIDE_CURSOR = "\x1b[?25l"
SHOW_CURSOR = "\x1b[?25h"
diff --git a/contrib/python/pip/pip/_internal/commands/__init__.py b/contrib/python/pip/pip/_internal/commands/__init__.py
index bc4f216a826..bedeca9e950 100644
--- a/contrib/python/pip/pip/_internal/commands/__init__.py
+++ b/contrib/python/pip/pip/_internal/commands/__init__.py
@@ -2,9 +2,11 @@
Package containing all pip commands
"""
+from __future__ import annotations
+
import importlib
from collections import namedtuple
-from typing import Any, Dict, Optional
+from typing import Any
from pip._internal.cli.base_command import Command
@@ -17,7 +19,7 @@ CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
# Even though the module path starts with the same "pip._internal.commands"
# prefix, the full path makes testing easier (specifically when modifying
# `commands_dict` in test setup / teardown).
-commands_dict: Dict[str, CommandInfo] = {
+commands_dict: dict[str, CommandInfo] = {
"install": CommandInfo(
"pip._internal.commands.install",
"InstallCommand",
@@ -123,7 +125,7 @@ def create_command(name: str, **kwargs: Any) -> Command:
return command
-def get_similar_commands(name: str) -> Optional[str]:
+def get_similar_commands(name: str) -> str | None:
"""Command name auto-correct."""
from difflib import get_close_matches
diff --git a/contrib/python/pip/pip/_internal/commands/cache.py b/contrib/python/pip/pip/_internal/commands/cache.py
index ad65641edb2..c8e7aede687 100644
--- a/contrib/python/pip/pip/_internal/commands/cache.py
+++ b/contrib/python/pip/pip/_internal/commands/cache.py
@@ -1,7 +1,7 @@
import os
import textwrap
from optparse import Values
-from typing import Any, List
+from typing import Callable
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -49,8 +49,8 @@ class CacheCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
- handlers = {
+ def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
+ return {
"dir": self.get_cache_dir,
"info": self.get_cache_info,
"list": self.list_cache_items,
@@ -58,15 +58,18 @@ class CacheCommand(Command):
"purge": self.purge_cache,
}
+ def run(self, options: Values, args: list[str]) -> int:
+ handler_map = self.handler_map()
+
if not options.cache_dir:
logger.error("pip cache commands can not function since cache is disabled.")
return ERROR
# Determine action
- if not args or args[0] not in handlers:
+ if not args or args[0] not in handler_map:
logger.error(
"Need an action (%s) to perform.",
- ", ".join(sorted(handlers)),
+ ", ".join(sorted(handler_map)),
)
return ERROR
@@ -74,20 +77,20 @@ class CacheCommand(Command):
# Error handling happens here, not in the action-handlers.
try:
- handlers[action](options, args[1:])
+ handler_map[action](options, args[1:])
except PipError as e:
logger.error(e.args[0])
return ERROR
return SUCCESS
- def get_cache_dir(self, options: Values, args: List[Any]) -> None:
+ def get_cache_dir(self, options: Values, args: list[str]) -> None:
if args:
raise CommandError("Too many arguments")
logger.info(options.cache_dir)
- def get_cache_info(self, options: Values, args: List[Any]) -> None:
+ def get_cache_info(self, options: Values, args: list[str]) -> None:
if args:
raise CommandError("Too many arguments")
@@ -129,7 +132,7 @@ class CacheCommand(Command):
logger.info(message)
- def list_cache_items(self, options: Values, args: List[Any]) -> None:
+ def list_cache_items(self, options: Values, args: list[str]) -> None:
if len(args) > 1:
raise CommandError("Too many arguments")
@@ -144,7 +147,7 @@ class CacheCommand(Command):
else:
self.format_for_abspath(files)
- def format_for_human(self, files: List[str]) -> None:
+ def format_for_human(self, files: list[str]) -> None:
if not files:
logger.info("No locally built wheels cached.")
return
@@ -157,11 +160,11 @@ class CacheCommand(Command):
logger.info("Cache contents:\n")
logger.info("\n".join(sorted(results)))
- def format_for_abspath(self, files: List[str]) -> None:
+ def format_for_abspath(self, files: list[str]) -> None:
if files:
logger.info("\n".join(sorted(files)))
- def remove_cache_items(self, options: Values, args: List[Any]) -> None:
+ def remove_cache_items(self, options: Values, args: list[str]) -> None:
if len(args) > 1:
raise CommandError("Too many arguments")
@@ -188,7 +191,7 @@ class CacheCommand(Command):
logger.verbose("Removed %s", filename)
logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
- def purge_cache(self, options: Values, args: List[Any]) -> None:
+ def purge_cache(self, options: Values, args: list[str]) -> None:
if args:
raise CommandError("Too many arguments")
@@ -197,14 +200,14 @@ class CacheCommand(Command):
def _cache_dir(self, options: Values, subdir: str) -> str:
return os.path.join(options.cache_dir, subdir)
- def _find_http_files(self, options: Values) -> List[str]:
+ def _find_http_files(self, options: Values) -> list[str]:
old_http_dir = self._cache_dir(options, "http")
new_http_dir = self._cache_dir(options, "http-v2")
return filesystem.find_files(old_http_dir, "*") + filesystem.find_files(
new_http_dir, "*"
)
- def _find_wheels(self, options: Values, pattern: str) -> List[str]:
+ def _find_wheels(self, options: Values, pattern: str) -> list[str]:
wheel_dir = self._cache_dir(options, "wheels")
# The wheel filename format, as specified in PEP 427, is:
diff --git a/contrib/python/pip/pip/_internal/commands/check.py b/contrib/python/pip/pip/_internal/commands/check.py
index f54a16dc0a1..516757eead7 100644
--- a/contrib/python/pip/pip/_internal/commands/check.py
+++ b/contrib/python/pip/pip/_internal/commands/check.py
@@ -1,6 +1,5 @@
import logging
from optparse import Values
-from typing import List
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -23,7 +22,7 @@ class CheckCommand(Command):
usage = """
%prog [options]"""
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
package_set, parsing_probs = create_package_set_from_installed()
missing, conflicting = check_package_set(package_set)
unsupported = list(
diff --git a/contrib/python/pip/pip/_internal/commands/completion.py b/contrib/python/pip/pip/_internal/commands/completion.py
index fe041d2951a..6d9597bdea0 100644
--- a/contrib/python/pip/pip/_internal/commands/completion.py
+++ b/contrib/python/pip/pip/_internal/commands/completion.py
@@ -1,7 +1,6 @@
import sys
import textwrap
from optparse import Values
-from typing import List
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
@@ -119,7 +118,7 @@ class CompletionCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
"""Prints the completion code of the given shell"""
shells = COMPLETION_SCRIPTS.keys()
shell_options = ["--" + shell for shell in sorted(shells)]
diff --git a/contrib/python/pip/pip/_internal/commands/configuration.py b/contrib/python/pip/pip/_internal/commands/configuration.py
index 1a1dc6b6cd8..7bcea043460 100644
--- a/contrib/python/pip/pip/_internal/commands/configuration.py
+++ b/contrib/python/pip/pip/_internal/commands/configuration.py
@@ -1,8 +1,10 @@
+from __future__ import annotations
+
import logging
import os
import subprocess
from optparse import Values
-from typing import Any, List, Optional
+from typing import Any, Callable
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -93,8 +95,8 @@ class ConfigurationCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
- handlers = {
+ def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
+ return {
"list": self.list_values,
"edit": self.open_in_editor,
"get": self.get_name,
@@ -103,11 +105,14 @@ class ConfigurationCommand(Command):
"debug": self.list_config_values,
}
+ def run(self, options: Values, args: list[str]) -> int:
+ handler_map = self.handler_map()
+
# Determine action
- if not args or args[0] not in handlers:
+ if not args or args[0] not in handler_map:
logger.error(
"Need an action (%s) to perform.",
- ", ".join(sorted(handlers)),
+ ", ".join(sorted(handler_map)),
)
return ERROR
@@ -131,14 +136,14 @@ class ConfigurationCommand(Command):
# Error handling happens here, not in the action-handlers.
try:
- handlers[action](options, args[1:])
+ handler_map[action](options, args[1:])
except PipError as e:
logger.error(e.args[0])
return ERROR
return SUCCESS
- def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
+ def _determine_file(self, options: Values, need_value: bool) -> Kind | None:
file_options = [
key
for key, value in (
@@ -168,31 +173,32 @@ class ConfigurationCommand(Command):
"(--user, --site, --global) to perform."
)
- def list_values(self, options: Values, args: List[str]) -> None:
+ def list_values(self, options: Values, args: list[str]) -> None:
self._get_n_args(args, "list", n=0)
for key, value in sorted(self.configuration.items()):
- write_output("%s=%r", key, value)
+ for key, value in sorted(value.items()):
+ write_output("%s=%r", key, value)
- def get_name(self, options: Values, args: List[str]) -> None:
+ def get_name(self, options: Values, args: list[str]) -> None:
key = self._get_n_args(args, "get [name]", n=1)
value = self.configuration.get_value(key)
write_output("%s", value)
- def set_name_value(self, options: Values, args: List[str]) -> None:
+ def set_name_value(self, options: Values, args: list[str]) -> None:
key, value = self._get_n_args(args, "set [name] [value]", n=2)
self.configuration.set_value(key, value)
self._save_configuration()
- def unset_name(self, options: Values, args: List[str]) -> None:
+ def unset_name(self, options: Values, args: list[str]) -> None:
key = self._get_n_args(args, "unset [name]", n=1)
self.configuration.unset_value(key)
self._save_configuration()
- def list_config_values(self, options: Values, args: List[str]) -> None:
+ def list_config_values(self, options: Values, args: list[str]) -> None:
"""List config key-value pairs across different config files"""
self._get_n_args(args, "debug", n=0)
@@ -206,13 +212,15 @@ class ConfigurationCommand(Command):
file_exists = os.path.exists(fname)
write_output("%s, exists: %r", fname, file_exists)
if file_exists:
- self.print_config_file_values(variant)
+ self.print_config_file_values(variant, fname)
- def print_config_file_values(self, variant: Kind) -> None:
+ def print_config_file_values(self, variant: Kind, fname: str) -> None:
"""Get key-value pairs from the file of a variant"""
for name, value in self.configuration.get_values_in_config(variant).items():
with indent_log():
- write_output("%s: %s", name, value)
+ if name == fname:
+ for confname, confvalue in value.items():
+ write_output("%s: %s", confname, confvalue)
def print_env_var_values(self) -> None:
"""Get key-values pairs present as environment variables"""
@@ -222,7 +230,7 @@ class ConfigurationCommand(Command):
env_var = f"PIP_{key.upper()}"
write_output("%s=%r", env_var, value)
- def open_in_editor(self, options: Values, args: List[str]) -> None:
+ def open_in_editor(self, options: Values, args: list[str]) -> None:
editor = self._determine_editor(options)
fname = self.configuration.get_file_to_edit()
@@ -244,7 +252,7 @@ class ConfigurationCommand(Command):
except subprocess.CalledProcessError as e:
raise PipError(f"Editor Subprocess exited with exit code {e.returncode}")
- def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
+ def _get_n_args(self, args: list[str], example: str, n: int) -> Any:
"""Helper to make sure the command got the right number of arguments"""
if len(args) != n:
msg = (
diff --git a/contrib/python/pip/pip/_internal/commands/debug.py b/contrib/python/pip/pip/_internal/commands/debug.py
index 567ca967e5b..0e187e79c28 100644
--- a/contrib/python/pip/pip/_internal/commands/debug.py
+++ b/contrib/python/pip/pip/_internal/commands/debug.py
@@ -1,10 +1,12 @@
+from __future__ import annotations
+
import locale
import logging
import os
import sys
from optparse import Values
from types import ModuleType
-from typing import Any, Dict, List, Optional
+from typing import Any
import pip._vendor
from pip._vendor.certifi import where
@@ -34,7 +36,7 @@ def show_sys_implementation() -> None:
show_value("name", implementation_name)
-def create_vendor_txt_map() -> Dict[str, str]:
+def create_vendor_txt_map() -> dict[str, str]:
with open_text_resource("pip._vendor", "vendor.txt") as f:
# Purge non version specifying lines.
# Also, remove any space prefix or suffixes (including comments).
@@ -46,7 +48,7 @@ def create_vendor_txt_map() -> Dict[str, str]:
return dict(line.split("==", 1) for line in lines)
-def get_module_from_module_name(module_name: str) -> Optional[ModuleType]:
+def get_module_from_module_name(module_name: str) -> ModuleType | None:
# Module name can be uppercase in vendor.txt for some reason...
module_name = module_name.lower().replace("-", "_")
# PATCH: setuptools is actually only pkg_resources.
@@ -64,7 +66,7 @@ def get_module_from_module_name(module_name: str) -> Optional[ModuleType]:
raise
-def get_vendor_version_from_module(module_name: str) -> Optional[str]:
+def get_vendor_version_from_module(module_name: str) -> str | None:
module = get_module_from_module_name(module_name)
version = getattr(module, "__version__", None)
@@ -79,7 +81,7 @@ def get_vendor_version_from_module(module_name: str) -> Optional[str]:
return version
-def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
+def show_actual_vendor_versions(vendor_txt_versions: dict[str, str]) -> None:
"""Log the actual version and print extra info if there is
a conflict or if the actual version could not be imported.
"""
@@ -169,7 +171,7 @@ class DebugCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
self.parser.config.load()
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
logger.warning(
"This command is only meant for debugging. "
"Do not use this with automation for parsing and getting these "
diff --git a/contrib/python/pip/pip/_internal/commands/download.py b/contrib/python/pip/pip/_internal/commands/download.py
index 917bbb91d83..900fb403d6f 100644
--- a/contrib/python/pip/pip/_internal/commands/download.py
+++ b/contrib/python/pip/pip/_internal/commands/download.py
@@ -1,7 +1,6 @@
import logging
import os
from optparse import Values
-from typing import List
from pip._internal.cli import cmdoptions
from pip._internal.cli.cmdoptions import make_target_python
@@ -75,7 +74,7 @@ class DownloadCommand(RequirementCommand):
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
options.ignore_installed = True
# editable doesn't really make sense for `pip download`, but the bowels
# of the RequirementSet code require that property.
@@ -131,7 +130,7 @@ class DownloadCommand(RequirementCommand):
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
- downloaded: List[str] = []
+ downloaded: list[str] = []
for req in requirement_set.requirements.values():
if req.satisfied_by is None:
assert req.name is not None
diff --git a/contrib/python/pip/pip/_internal/commands/freeze.py b/contrib/python/pip/pip/_internal/commands/freeze.py
index f8de335b283..7794857cf80 100644
--- a/contrib/python/pip/pip/_internal/commands/freeze.py
+++ b/contrib/python/pip/pip/_internal/commands/freeze.py
@@ -1,6 +1,5 @@
import sys
from optparse import Values
-from typing import AbstractSet, List
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
@@ -13,7 +12,7 @@ def _should_suppress_build_backends() -> bool:
return sys.version_info < (3, 12)
-def _dev_pkgs() -> AbstractSet[str]:
+def _dev_pkgs() -> set[str]:
pkgs = {"pip"}
if _should_suppress_build_backends():
@@ -85,7 +84,7 @@ class FreezeCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
skip = set(stdlib_pkgs)
if not options.freeze_all:
skip.update(_dev_pkgs())
diff --git a/contrib/python/pip/pip/_internal/commands/hash.py b/contrib/python/pip/pip/_internal/commands/hash.py
index 042dac813e7..271a4c91a7f 100644
--- a/contrib/python/pip/pip/_internal/commands/hash.py
+++ b/contrib/python/pip/pip/_internal/commands/hash.py
@@ -2,7 +2,6 @@ import hashlib
import logging
import sys
from optparse import Values
-from typing import List
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -37,7 +36,7 @@ class HashCommand(Command):
)
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
if not args:
self.parser.print_usage(sys.stderr)
return ERROR
diff --git a/contrib/python/pip/pip/_internal/commands/help.py b/contrib/python/pip/pip/_internal/commands/help.py
index 62066318b74..2ae658ff5eb 100644
--- a/contrib/python/pip/pip/_internal/commands/help.py
+++ b/contrib/python/pip/pip/_internal/commands/help.py
@@ -1,5 +1,4 @@
from optparse import Values
-from typing import List
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
@@ -13,7 +12,7 @@ class HelpCommand(Command):
%prog <command>"""
ignore_require_venv = True
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
from pip._internal.commands import (
commands_dict,
create_command,
diff --git a/contrib/python/pip/pip/_internal/commands/index.py b/contrib/python/pip/pip/_internal/commands/index.py
index e8714a7270c..ecac99888db 100644
--- a/contrib/python/pip/pip/_internal/commands/index.py
+++ b/contrib/python/pip/pip/_internal/commands/index.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
import json
import logging
+from collections.abc import Iterable
from optparse import Values
-from typing import Any, Iterable, List, Optional
+from typing import Any, Callable
from pip._vendor.packaging.version import Version
@@ -50,16 +53,19 @@ class IndexCommand(IndexGroupCommand):
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
- handlers = {
+ def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
+ return {
"versions": self.get_available_package_versions,
}
+ def run(self, options: Values, args: list[str]) -> int:
+ handler_map = self.handler_map()
+
# Determine action
- if not args or args[0] not in handlers:
+ if not args or args[0] not in handler_map:
logger.error(
"Need an action (%s) to perform.",
- ", ".join(sorted(handlers)),
+ ", ".join(sorted(handler_map)),
)
return ERROR
@@ -67,7 +73,7 @@ class IndexCommand(IndexGroupCommand):
# Error handling happens here, not in the action-handlers.
try:
- handlers[action](options, args[1:])
+ handler_map[action](options, args[1:])
except PipError as e:
logger.error(e.args[0])
return ERROR
@@ -78,8 +84,8 @@ class IndexCommand(IndexGroupCommand):
self,
options: Values,
session: PipSession,
- target_python: Optional[TargetPython] = None,
- ignore_requires_python: Optional[bool] = None,
+ target_python: TargetPython | None = None,
+ ignore_requires_python: bool | None = None,
) -> PackageFinder:
"""
Create a package finder appropriate to the index command.
@@ -99,7 +105,7 @@ class IndexCommand(IndexGroupCommand):
target_python=target_python,
)
- def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
+ def get_available_package_versions(self, options: Values, args: list[Any]) -> None:
if len(args) != 1:
raise CommandError("You need to specify exactly one argument")
diff --git a/contrib/python/pip/pip/_internal/commands/inspect.py b/contrib/python/pip/pip/_internal/commands/inspect.py
index e810c13166b..e262012ee4d 100644
--- a/contrib/python/pip/pip/_internal/commands/inspect.py
+++ b/contrib/python/pip/pip/_internal/commands/inspect.py
@@ -1,6 +1,6 @@
import logging
from optparse import Values
-from typing import Any, Dict, List
+from typing import Any
from pip._vendor.packaging.markers import default_environment
from pip._vendor.rich import print_json
@@ -45,7 +45,7 @@ class InspectCommand(Command):
self.cmd_opts.add_option(cmdoptions.list_path())
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
cmdoptions.check_list_path_option(options)
dists = get_environment(options.path).iter_installed_distributions(
local_only=options.local,
@@ -62,8 +62,8 @@ class InspectCommand(Command):
print_json(data=output)
return SUCCESS
- def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
- res: Dict[str, Any] = {
+ def _dist_to_dict(self, dist: BaseDistribution) -> dict[str, Any]:
+ res: dict[str, Any] = {
"metadata": dist.metadata_dict,
"metadata_location": dist.info_location,
}
diff --git a/contrib/python/pip/pip/_internal/commands/install.py b/contrib/python/pip/pip/_internal/commands/install.py
index 300ae92b337..1ef7a0f4410 100644
--- a/contrib/python/pip/pip/_internal/commands/install.py
+++ b/contrib/python/pip/pip/_internal/commands/install.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import errno
import json
import operator
@@ -5,7 +7,7 @@ import os
import shutil
import site
from optparse import SUPPRESS_HELP, Values
-from typing import List, Optional
+from pathlib import Path
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.requests.exceptions import InvalidProxyURL
@@ -26,7 +28,11 @@ from pip._internal.cli.req_command import (
with_cleanup,
)
from pip._internal.cli.status_codes import ERROR, SUCCESS
-from pip._internal.exceptions import CommandError, InstallationError
+from pip._internal.exceptions import (
+ CommandError,
+ InstallationError,
+ InstallWheelBuildError,
+)
from pip._internal.locations import get_scheme
from pip._internal.metadata import get_environment
from pip._internal.models.installation_report import InstallationReport
@@ -272,7 +278,7 @@ class InstallCommand(RequirementCommand):
)
@with_cleanup
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
if options.use_user_site and options.target_dir is not None:
raise CommandError("Can not combine '--user' and '--target'")
@@ -308,8 +314,8 @@ class InstallCommand(RequirementCommand):
isolated_mode=options.isolated_mode,
)
- target_temp_dir: Optional[TempDirectory] = None
- target_temp_dir_path: Optional[str] = None
+ target_temp_dir: TempDirectory | None = None
+ target_temp_dir_path: str | None = None
if options.target_dir:
options.ignore_installed = True
options.target_dir = os.path.abspath(options.target_dir)
@@ -433,17 +439,12 @@ class InstallCommand(RequirementCommand):
)
if build_failures:
- raise InstallationError(
- "Failed to build installable wheels for some "
- "pyproject.toml based projects ({})".format(
- ", ".join(r.name for r in build_failures) # type: ignore
- )
- )
+ raise InstallWheelBuildError(build_failures)
to_install = resolver.get_installation_order(requirement_set)
# Check for conflicts in the package set we're installing.
- conflicts: Optional[ConflictDetails] = None
+ conflicts: ConflictDetails | None = None
should_warn_about_conflicts = (
not options.ignore_dependencies and options.warn_about_conflicts
)
@@ -581,8 +582,8 @@ class InstallCommand(RequirementCommand):
shutil.move(os.path.join(lib_dir, item), target_item_dir)
def _determine_conflicts(
- self, to_install: List[InstallRequirement]
- ) -> Optional[ConflictDetails]:
+ self, to_install: list[InstallRequirement]
+ ) -> ConflictDetails | None:
try:
return check_install_conflicts(to_install)
except Exception:
@@ -599,7 +600,7 @@ class InstallCommand(RequirementCommand):
if not missing and not conflicting:
return
- parts: List[str] = []
+ parts: list[str] = []
if resolver_variant == "legacy":
parts.append(
"pip's legacy dependency resolver does not consider dependency "
@@ -645,11 +646,11 @@ class InstallCommand(RequirementCommand):
def get_lib_location_guesses(
user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
+ home: str | None = None,
+ root: str | None = None,
isolated: bool = False,
- prefix: Optional[str] = None,
-) -> List[str]:
+ prefix: str | None = None,
+) -> list[str]:
scheme = get_scheme(
"",
user=user,
@@ -661,7 +662,7 @@ def get_lib_location_guesses(
return [scheme.purelib, scheme.platlib]
-def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
+def site_packages_writable(root: str | None, isolated: bool) -> bool:
return all(
test_writable_dir(d)
for d in set(get_lib_location_guesses(root=root, isolated=isolated))
@@ -669,10 +670,10 @@ def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
def decide_user_install(
- use_user_site: Optional[bool],
- prefix_path: Optional[str] = None,
- target_dir: Optional[str] = None,
- root_path: Optional[str] = None,
+ use_user_site: bool | None,
+ prefix_path: str | None = None,
+ target_dir: str | None = None,
+ root_path: str | None = None,
isolated_mode: bool = False,
) -> bool:
"""Determine whether to do a user install based on the input options.
@@ -774,20 +775,24 @@ def create_os_error_message(
)
parts.append(".\n")
- # Suggest the user to enable Long Paths if path length is
- # more than 260
- if (
- WINDOWS
- and error.errno == errno.ENOENT
- and error.filename
- and len(error.filename) > 260
- ):
- parts.append(
- "HINT: This error might have occurred since "
- "this system does not have Windows Long Path "
- "support enabled. You can find information on "
- "how to enable this at "
- "https://pip.pypa.io/warnings/enable-long-paths\n"
- )
+ # On Windows, errors like EINVAL or ENOENT may occur
+ # if a file or folder name exceeds 255 characters,
+ # or if the full path exceeds 260 characters and long path support isn't enabled.
+ # This condition checks for such cases and adds a hint to the error output.
+ if WINDOWS and error.errno in (errno.EINVAL, errno.ENOENT) and error.filename:
+ if any(len(part) > 255 for part in Path(error.filename).parts):
+ parts.append(
+ "HINT: This error might be caused by a file or folder name exceeding "
+ "255 characters, which is a Windows limitation even if long paths "
+ "are enabled.\n "
+ )
+ if len(error.filename) > 260:
+ parts.append(
+ "HINT: This error might have occurred since "
+ "this system does not have Windows Long Path "
+ "support enabled. You can find information on "
+ "how to enable this at "
+ "https://pip.pypa.io/warnings/enable-long-paths\n"
+ )
return "".join(parts).strip() + "\n"
diff --git a/contrib/python/pip/pip/_internal/commands/list.py b/contrib/python/pip/pip/_internal/commands/list.py
index b03085070d2..ad27e45ce93 100644
--- a/contrib/python/pip/pip/_internal/commands/list.py
+++ b/contrib/python/pip/pip/_internal/commands/list.py
@@ -1,11 +1,14 @@
+from __future__ import annotations
+
import json
import logging
+from collections.abc import Generator, Sequence
from email.parser import Parser
from optparse import Values
-from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
+from typing import TYPE_CHECKING, cast
from pip._vendor.packaging.utils import canonicalize_name
-from pip._vendor.packaging.version import Version
+from pip._vendor.packaging.version import InvalidVersion, Version
from pip._internal.cli import cmdoptions
from pip._internal.cli.index_command import IndexGroupCommand
@@ -140,8 +143,8 @@ class ListCommand(IndexGroupCommand):
super().handle_pip_version_check(options)
def _build_package_finder(
- self, options: Values, session: "PipSession"
- ) -> "PackageFinder":
+ self, options: Values, session: PipSession
+ ) -> PackageFinder:
"""
Create a package finder appropriate to this list command.
"""
@@ -162,7 +165,7 @@ class ListCommand(IndexGroupCommand):
selection_prefs=selection_prefs,
)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
if options.outdated and options.uptodate:
raise CommandError("Options --outdated and --uptodate cannot be combined.")
@@ -204,8 +207,8 @@ class ListCommand(IndexGroupCommand):
return SUCCESS
def get_outdated(
- self, packages: "_ProcessedDists", options: Values
- ) -> "_ProcessedDists":
+ self, packages: _ProcessedDists, options: Values
+ ) -> _ProcessedDists:
return [
dist
for dist in self.iter_packages_latest_infos(packages, options)
@@ -213,8 +216,8 @@ class ListCommand(IndexGroupCommand):
]
def get_uptodate(
- self, packages: "_ProcessedDists", options: Values
- ) -> "_ProcessedDists":
+ self, packages: _ProcessedDists, options: Values
+ ) -> _ProcessedDists:
return [
dist
for dist in self.iter_packages_latest_infos(packages, options)
@@ -222,8 +225,8 @@ class ListCommand(IndexGroupCommand):
]
def get_not_required(
- self, packages: "_ProcessedDists", options: Values
- ) -> "_ProcessedDists":
+ self, packages: _ProcessedDists, options: Values
+ ) -> _ProcessedDists:
dep_keys = {
canonicalize_name(dep.name)
for dist in packages
@@ -236,14 +239,14 @@ class ListCommand(IndexGroupCommand):
return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys})
def iter_packages_latest_infos(
- self, packages: "_ProcessedDists", options: Values
- ) -> Generator["_DistWithLatestInfo", None, None]:
+ self, packages: _ProcessedDists, options: Values
+ ) -> Generator[_DistWithLatestInfo, None, None]:
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
def latest_info(
- dist: "_DistWithLatestInfo",
- ) -> Optional["_DistWithLatestInfo"]:
+ dist: _DistWithLatestInfo,
+ ) -> _DistWithLatestInfo | None:
all_candidates = finder.find_all_candidates(dist.canonical_name)
if not options.pre:
# Remove prereleases
@@ -274,7 +277,7 @@ class ListCommand(IndexGroupCommand):
yield dist
def output_package_listing(
- self, packages: "_ProcessedDists", options: Values
+ self, packages: _ProcessedDists, options: Values
) -> None:
packages = sorted(
packages,
@@ -285,17 +288,19 @@ class ListCommand(IndexGroupCommand):
self.output_package_listing_columns(data, header)
elif options.list_format == "freeze":
for dist in packages:
+ try:
+ req_string = f"{dist.raw_name}=={dist.version}"
+ except InvalidVersion:
+ req_string = f"{dist.raw_name}==={dist.raw_version}"
if options.verbose >= 1:
- write_output(
- "%s==%s (%s)", dist.raw_name, dist.version, dist.location
- )
+ write_output("%s (%s)", req_string, dist.location)
else:
- write_output("%s==%s", dist.raw_name, dist.version)
+ write_output(req_string)
elif options.list_format == "json":
write_output(format_for_json(packages, options))
def output_package_listing_columns(
- self, data: List[List[str]], header: List[str]
+ self, data: list[list[str]], header: list[str]
) -> None:
# insert the header first: we need to know the size of column names
if len(data) > 0:
@@ -312,8 +317,8 @@ class ListCommand(IndexGroupCommand):
def format_for_columns(
- pkgs: "_ProcessedDists", options: Values
-) -> Tuple[List[List[str]], List[str]]:
+ pkgs: _ProcessedDists, options: Values
+) -> tuple[list[list[str]], list[str]]:
"""
Convert the package data into something usable
by output_package_listing_columns.
@@ -324,7 +329,7 @@ def format_for_columns(
if running_outdated:
header.extend(["Latest", "Type"])
- def wheel_build_tag(dist: BaseDistribution) -> Optional[str]:
+ def wheel_build_tag(dist: BaseDistribution) -> str | None:
try:
wheel_file = dist.read_text("WHEEL")
except FileNotFoundError:
@@ -371,12 +376,16 @@ def format_for_columns(
return data, header
-def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
+def format_for_json(packages: _ProcessedDists, options: Values) -> str:
data = []
for dist in packages:
+ try:
+ version = str(dist.version)
+ except InvalidVersion:
+ version = dist.raw_version
info = {
"name": dist.raw_name,
- "version": str(dist.version),
+ "version": version,
}
if options.verbose >= 1:
info["location"] = dist.location or ""
diff --git a/contrib/python/pip/pip/_internal/commands/lock.py b/contrib/python/pip/pip/_internal/commands/lock.py
index 39f27e8677b..e4a978d5aaa 100644
--- a/contrib/python/pip/pip/_internal/commands/lock.py
+++ b/contrib/python/pip/pip/_internal/commands/lock.py
@@ -1,7 +1,6 @@
import sys
from optparse import Values
from pathlib import Path
-from typing import List
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
@@ -90,7 +89,7 @@ class LockCommand(RequirementCommand):
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
logger.verbose("Using %s", get_pip_version())
logger.warning(
diff --git a/contrib/python/pip/pip/_internal/commands/search.py b/contrib/python/pip/pip/_internal/commands/search.py
index c58c2b3b11e..b8dbc27d3ac 100644
--- a/contrib/python/pip/pip/_internal/commands/search.py
+++ b/contrib/python/pip/pip/_internal/commands/search.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import logging
import shutil
import sys
@@ -5,7 +7,7 @@ import textwrap
import xmlrpc.client
from collections import OrderedDict
from optparse import Values
-from typing import Dict, List, Optional, TypedDict
+from typing import TypedDict
from pip._vendor.packaging.version import parse as parse_version
@@ -24,7 +26,7 @@ from pip._internal.utils.misc import write_output
class TransformedHit(TypedDict):
name: str
summary: str
- versions: List[str]
+ versions: list[str]
logger = logging.getLogger(__name__)
@@ -49,7 +51,7 @@ class SearchCommand(Command, SessionCommandMixin):
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
if not args:
raise CommandError("Missing required argument (search query).")
query = args
@@ -65,7 +67,7 @@ class SearchCommand(Command, SessionCommandMixin):
return SUCCESS
return NO_MATCHES_FOUND
- def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
+ def search(self, query: list[str], options: Values) -> list[dict[str, str]]:
index_url = options.index
session = self.get_default_session(options)
@@ -83,13 +85,13 @@ class SearchCommand(Command, SessionCommandMixin):
return hits
-def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
+def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
"""
The list from pypi is really a list of versions. We want a list of
packages with the list of versions stored inline. This converts the
list from pypi into one we can use.
"""
- packages: Dict[str, TransformedHit] = OrderedDict()
+ packages: dict[str, TransformedHit] = OrderedDict()
for hit in hits:
name = hit["name"]
summary = hit["summary"]
@@ -111,7 +113,7 @@ def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
return list(packages.values())
-def print_dist_installation_info(latest: str, dist: Optional[BaseDistribution]) -> None:
+def print_dist_installation_info(latest: str, dist: BaseDistribution | None) -> None:
if dist is not None:
with indent_log():
if dist.version == latest:
@@ -128,15 +130,15 @@ def print_dist_installation_info(latest: str, dist: Optional[BaseDistribution])
write_output("LATEST: %s", latest)
-def get_installed_distribution(name: str) -> Optional[BaseDistribution]:
+def get_installed_distribution(name: str) -> BaseDistribution | None:
env = get_default_environment()
return env.get_distribution(name)
def print_results(
- hits: List["TransformedHit"],
- name_column_width: Optional[int] = None,
- terminal_width: Optional[int] = None,
+ hits: list[TransformedHit],
+ name_column_width: int | None = None,
+ terminal_width: int | None = None,
) -> None:
if not hits:
return
@@ -172,5 +174,5 @@ def print_results(
pass
-def highest_version(versions: List[str]) -> str:
+def highest_version(versions: list[str]) -> str:
return max(versions, key=parse_version)
diff --git a/contrib/python/pip/pip/_internal/commands/show.py b/contrib/python/pip/pip/_internal/commands/show.py
index 7aaf6f4b6ca..f9fcfa60bcb 100644
--- a/contrib/python/pip/pip/_internal/commands/show.py
+++ b/contrib/python/pip/pip/_internal/commands/show.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
import logging
import string
+from collections.abc import Generator, Iterable, Iterator
from optparse import Values
-from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional
+from typing import NamedTuple
from pip._vendor.packaging.requirements import InvalidRequirement
from pip._vendor.packaging.utils import canonicalize_name
@@ -44,7 +47,7 @@ class ShowCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
if not args:
logger.warning("ERROR: Please provide a package name or names.")
return ERROR
@@ -62,24 +65,24 @@ class _PackageInfo(NamedTuple):
name: str
version: str
location: str
- editable_project_location: Optional[str]
- requires: List[str]
- required_by: List[str]
+ editable_project_location: str | None
+ requires: list[str]
+ required_by: list[str]
installer: str
metadata_version: str
- classifiers: List[str]
+ classifiers: list[str]
summary: str
homepage: str
- project_urls: List[str]
+ project_urls: list[str]
author: str
author_email: str
license: str
license_expression: str
- entry_points: List[str]
- files: Optional[List[str]]
+ entry_points: list[str]
+ files: list[str] | None
-def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]:
+def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None]:
"""
Gather details from installed distributions. Print distribution name,
version, location, and installed files. Installed files requires a
@@ -132,7 +135,7 @@ def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None
files_iter = dist.iter_declared_entries()
if files_iter is None:
- files: Optional[List[str]] = None
+ files: list[str] | None = None
else:
files = sorted(files_iter)
diff --git a/contrib/python/pip/pip/_internal/commands/uninstall.py b/contrib/python/pip/pip/_internal/commands/uninstall.py
index bc0edeac9fb..9c4f031f934 100644
--- a/contrib/python/pip/pip/_internal/commands/uninstall.py
+++ b/contrib/python/pip/pip/_internal/commands/uninstall.py
@@ -1,6 +1,5 @@
import logging
from optparse import Values
-from typing import List
from pip._vendor.packaging.utils import canonicalize_name
@@ -62,7 +61,7 @@ class UninstallCommand(Command, SessionCommandMixin):
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
session = self.get_default_session(options)
reqs_to_uninstall = {}
diff --git a/contrib/python/pip/pip/_internal/commands/wheel.py b/contrib/python/pip/pip/_internal/commands/wheel.py
index b380754bc89..61be254912f 100644
--- a/contrib/python/pip/pip/_internal/commands/wheel.py
+++ b/contrib/python/pip/pip/_internal/commands/wheel.py
@@ -2,7 +2,6 @@ import logging
import os
import shutil
from optparse import Values
-from typing import List
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
@@ -101,7 +100,7 @@ class WheelCommand(RequirementCommand):
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
- def run(self, options: Values, args: List[str]) -> int:
+ def run(self, options: Values, args: list[str]) -> int:
session = self.get_default_session(options)
finder = self._build_package_finder(options, session)
@@ -146,7 +145,7 @@ class WheelCommand(RequirementCommand):
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
- reqs_to_build: List[InstallRequirement] = []
+ reqs_to_build: list[InstallRequirement] = []
for req in requirement_set.requirements.values():
if req.is_wheel:
preparer.save_linked_requirement(req)
diff --git a/contrib/python/pip/pip/_internal/configuration.py b/contrib/python/pip/pip/_internal/configuration.py
index ffeda1d47a1..f581a2c950a 100644
--- a/contrib/python/pip/pip/_internal/configuration.py
+++ b/contrib/python/pip/pip/_internal/configuration.py
@@ -11,11 +11,14 @@ Some terminology:
A single word describing where the configuration key-value pair came from
"""
+from __future__ import annotations
+
import configparser
import locale
import os
import sys
-from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
+from collections.abc import Iterable
+from typing import Any, NewType
from pip._internal.exceptions import (
ConfigurationError,
@@ -55,7 +58,7 @@ def _normalize_name(name: str) -> str:
return name
-def _disassemble_key(name: str) -> List[str]:
+def _disassemble_key(name: str) -> list[str]:
if "." not in name:
error_message = (
"Key does not contain dot separated section and key. "
@@ -65,7 +68,7 @@ def _disassemble_key(name: str) -> List[str]:
return name.split(".", 1)
-def get_configuration_files() -> Dict[Kind, List[str]]:
+def get_configuration_files() -> dict[Kind, list[str]]:
global_config_files = [
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
]
@@ -98,7 +101,7 @@ class Configuration:
and the data stored is also nice.
"""
- def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None:
+ def __init__(self, isolated: bool, load_only: Kind | None = None) -> None:
super().__init__()
if load_only is not None and load_only not in VALID_LOAD_ONLY:
@@ -111,13 +114,13 @@ class Configuration:
self.load_only = load_only
# Because we keep track of where we got the data from
- self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
+ self._parsers: dict[Kind, list[tuple[str, RawConfigParser]]] = {
variant: [] for variant in OVERRIDE_ORDER
}
- self._config: Dict[Kind, Dict[str, Any]] = {
+ self._config: dict[Kind, dict[str, dict[str, Any]]] = {
variant: {} for variant in OVERRIDE_ORDER
}
- self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
+ self._modified_parsers: list[tuple[str, RawConfigParser]] = []
def load(self) -> None:
"""Loads configuration from configuration files and environment"""
@@ -125,7 +128,7 @@ class Configuration:
if not self.isolated:
self._load_environment_vars()
- def get_file_to_edit(self) -> Optional[str]:
+ def get_file_to_edit(self) -> str | None:
"""Returns the file with highest priority in configuration"""
assert self.load_only is not None, "Need to be specified a file to be editing"
@@ -134,7 +137,7 @@ class Configuration:
except IndexError:
return None
- def items(self) -> Iterable[Tuple[str, Any]]:
+ def items(self) -> Iterable[tuple[str, Any]]:
"""Returns key-value pairs like dict.items() representing the loaded
configuration
"""
@@ -145,7 +148,10 @@ class Configuration:
orig_key = key
key = _normalize_name(key)
try:
- return self._dictionary[key]
+ clean_config: dict[str, Any] = {}
+ for file_values in self._dictionary.values():
+ clean_config.update(file_values)
+ return clean_config[key]
except KeyError:
# disassembling triggers a more useful error message than simply
# "No such key" in the case that the key isn't in the form command.option
@@ -168,7 +174,8 @@ class Configuration:
parser.add_section(section)
parser.set(section, name, value)
- self._config[self.load_only][key] = value
+ self._config[self.load_only].setdefault(fname, {})
+ self._config[self.load_only][fname][key] = value
self._mark_as_modified(fname, parser)
def unset_value(self, key: str) -> None:
@@ -178,11 +185,14 @@ class Configuration:
self._ensure_have_load_only()
assert self.load_only
- if key not in self._config[self.load_only]:
- raise ConfigurationError(f"No such key - {orig_key}")
-
fname, parser = self._get_parser_to_modify()
+ if (
+ key not in self._config[self.load_only][fname]
+ and key not in self._config[self.load_only]
+ ):
+ raise ConfigurationError(f"No such key - {orig_key}")
+
if parser is not None:
section, name = _disassemble_key(key)
if not (
@@ -197,8 +207,10 @@ class Configuration:
if not parser.items(section):
parser.remove_section(section)
self._mark_as_modified(fname, parser)
-
- del self._config[self.load_only][key]
+ try:
+ del self._config[self.load_only][fname][key]
+ except KeyError:
+ del self._config[self.load_only][key]
def save(self) -> None:
"""Save the current in-memory state."""
@@ -230,7 +242,7 @@ class Configuration:
logger.debug("Will be working with %s variant only", self.load_only)
@property
- def _dictionary(self) -> Dict[str, Any]:
+ def _dictionary(self) -> dict[str, dict[str, Any]]:
"""A dictionary representing the loaded configuration."""
# NOTE: Dictionaries are not populated if not loaded. So, conditionals
# are not needed here.
@@ -270,7 +282,8 @@ class Configuration:
for section in parser.sections():
items = parser.items(section)
- self._config[variant].update(self._normalized_keys(section, items))
+ self._config[variant].setdefault(fname, {})
+ self._config[variant][fname].update(self._normalized_keys(section, items))
return parser
@@ -297,13 +310,14 @@ class Configuration:
def _load_environment_vars(self) -> None:
"""Loads configuration from environment variables"""
- self._config[kinds.ENV_VAR].update(
+ self._config[kinds.ENV_VAR].setdefault(":env:", {})
+ self._config[kinds.ENV_VAR][":env:"].update(
self._normalized_keys(":env:", self.get_environ_vars())
)
def _normalized_keys(
- self, section: str, items: Iterable[Tuple[str, Any]]
- ) -> Dict[str, Any]:
+ self, section: str, items: Iterable[tuple[str, Any]]
+ ) -> dict[str, Any]:
"""Normalizes items to construct a dictionary with normalized keys.
This routine is where the names become keys and are made the same
@@ -315,7 +329,7 @@ class Configuration:
normalized[key] = val
return normalized
- def get_environ_vars(self) -> Iterable[Tuple[str, str]]:
+ def get_environ_vars(self) -> Iterable[tuple[str, str]]:
"""Returns a generator with all environmental vars with prefix PIP_"""
for key, val in os.environ.items():
if key.startswith("PIP_"):
@@ -324,7 +338,7 @@ class Configuration:
yield name, val
# XXX: This is patched in the tests.
- def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
+ def iter_config_files(self) -> Iterable[tuple[Kind, list[str]]]:
"""Yields variant and configuration files associated with it.
This should be treated like items of a dictionary. The order
@@ -356,11 +370,11 @@ class Configuration:
else:
yield kinds.ENV, []
- def get_values_in_config(self, variant: Kind) -> Dict[str, Any]:
+ def get_values_in_config(self, variant: Kind) -> dict[str, Any]:
"""Get values present in a config file"""
return self._config[variant]
- def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]:
+ def _get_parser_to_modify(self) -> tuple[str, RawConfigParser]:
# Determine which parser to modify
assert self.load_only
parsers = self._parsers[self.load_only]
diff --git a/contrib/python/pip/pip/_internal/distributions/base.py b/contrib/python/pip/pip/_internal/distributions/base.py
index 6e4d0c91a90..ea61f3501e7 100644
--- a/contrib/python/pip/pip/_internal/distributions/base.py
+++ b/contrib/python/pip/pip/_internal/distributions/base.py
@@ -1,11 +1,13 @@
+from __future__ import annotations
+
import abc
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING
from pip._internal.metadata.base import BaseDistribution
from pip._internal.req import InstallRequirement
if TYPE_CHECKING:
- from pip._internal.index.package_finder import PackageFinder
+ from pip._internal.build_env import BuildEnvironmentInstaller
class AbstractDistribution(metaclass=abc.ABCMeta):
@@ -32,7 +34,7 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
self.req = req
@abc.abstractproperty
- def build_tracker_id(self) -> Optional[str]:
+ def build_tracker_id(self) -> str | None:
"""A string that uniquely identifies this requirement to the build tracker.
If None, then this dist has no work to do in the build tracker, and
@@ -46,7 +48,7 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
@abc.abstractmethod
def prepare_distribution_metadata(
self,
- finder: "PackageFinder",
+ build_env_installer: BuildEnvironmentInstaller,
build_isolation: bool,
check_build_deps: bool,
) -> None:
diff --git a/contrib/python/pip/pip/_internal/distributions/installed.py b/contrib/python/pip/pip/_internal/distributions/installed.py
index ab8d53be740..b6a67df24f4 100644
--- a/contrib/python/pip/pip/_internal/distributions/installed.py
+++ b/contrib/python/pip/pip/_internal/distributions/installed.py
@@ -1,9 +1,13 @@
-from typing import Optional
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from pip._internal.distributions.base import AbstractDistribution
-from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution
+if TYPE_CHECKING:
+ from pip._internal.build_env import BuildEnvironmentInstaller
+
class InstalledDistribution(AbstractDistribution):
"""Represents an installed package.
@@ -13,7 +17,7 @@ class InstalledDistribution(AbstractDistribution):
"""
@property
- def build_tracker_id(self) -> Optional[str]:
+ def build_tracker_id(self) -> str | None:
return None
def get_metadata_distribution(self) -> BaseDistribution:
@@ -22,7 +26,7 @@ class InstalledDistribution(AbstractDistribution):
def prepare_distribution_metadata(
self,
- finder: PackageFinder,
+ build_env_installer: BuildEnvironmentInstaller,
build_isolation: bool,
check_build_deps: bool,
) -> None:
diff --git a/contrib/python/pip/pip/_internal/distributions/sdist.py b/contrib/python/pip/pip/_internal/distributions/sdist.py
index 28ea5cea16c..e2821f89e00 100644
--- a/contrib/python/pip/pip/_internal/distributions/sdist.py
+++ b/contrib/python/pip/pip/_internal/distributions/sdist.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
import logging
-from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
+from collections.abc import Iterable
+from typing import TYPE_CHECKING
from pip._internal.build_env import BuildEnvironment
from pip._internal.distributions.base import AbstractDistribution
@@ -8,7 +11,7 @@ from pip._internal.metadata import BaseDistribution
from pip._internal.utils.subprocess import runner_with_spinner_message
if TYPE_CHECKING:
- from pip._internal.index.package_finder import PackageFinder
+ from pip._internal.build_env import BuildEnvironmentInstaller
logger = logging.getLogger(__name__)
@@ -21,7 +24,7 @@ class SourceDistribution(AbstractDistribution):
"""
@property
- def build_tracker_id(self) -> Optional[str]:
+ def build_tracker_id(self) -> str | None:
"""Identify this requirement uniquely by its link."""
assert self.req.link
return self.req.link.url_without_fragment
@@ -31,7 +34,7 @@ class SourceDistribution(AbstractDistribution):
def prepare_distribution_metadata(
self,
- finder: "PackageFinder",
+ build_env_installer: BuildEnvironmentInstaller,
build_isolation: bool,
check_build_deps: bool,
) -> None:
@@ -43,7 +46,7 @@ class SourceDistribution(AbstractDistribution):
if should_isolate:
# Setup an isolated environment and install the build backend static
# requirements in it.
- self._prepare_build_backend(finder)
+ self._prepare_build_backend(build_env_installer)
# Check that if the requirement is editable, it either supports PEP 660 or
# has a setup.py or a setup.cfg. This cannot be done earlier because we need
# to setup the build backend to verify it supports build_editable, nor can
@@ -53,7 +56,7 @@ class SourceDistribution(AbstractDistribution):
# without setup.py nor setup.cfg.
self.req.isolated_editable_sanity_check()
# Install the dynamic build requirements.
- self._install_build_reqs(finder)
+ self._install_build_reqs(build_env_installer)
# Check if the current environment provides build dependencies
should_check_deps = self.req.use_pep517 and check_build_deps
if should_check_deps:
@@ -68,15 +71,17 @@ class SourceDistribution(AbstractDistribution):
self._raise_missing_reqs(missing)
self.req.prepare_metadata()
- def _prepare_build_backend(self, finder: "PackageFinder") -> None:
+ def _prepare_build_backend(
+ self, build_env_installer: BuildEnvironmentInstaller
+ ) -> None:
# Isolate in a BuildEnvironment and install the build-time
# requirements.
pyproject_requires = self.req.pyproject_requires
assert pyproject_requires is not None
- self.req.build_env = BuildEnvironment()
+ self.req.build_env = BuildEnvironment(build_env_installer)
self.req.build_env.install_requirements(
- finder, pyproject_requires, "overlay", kind="build dependencies"
+ pyproject_requires, "overlay", kind="build dependencies", for_req=self.req
)
conflicting, missing = self.req.build_env.check_requirements(
self.req.requirements_to_check
@@ -112,7 +117,9 @@ class SourceDistribution(AbstractDistribution):
with backend.subprocess_runner(runner):
return backend.get_requires_for_build_editable()
- def _install_build_reqs(self, finder: "PackageFinder") -> None:
+ def _install_build_reqs(
+ self, build_env_installer: BuildEnvironmentInstaller
+ ) -> None:
# Install any extra build dependencies that the backend requests.
# This must be done in a second pass, as the pyproject.toml
# dependencies must be installed before we can call the backend.
@@ -128,11 +135,11 @@ class SourceDistribution(AbstractDistribution):
if conflicting:
self._raise_conflicts("the backend dependencies", conflicting)
self.req.build_env.install_requirements(
- finder, missing, "normal", kind="backend dependencies"
+ missing, "normal", kind="backend dependencies", for_req=self.req
)
def _raise_conflicts(
- self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
+ self, conflicting_with: str, conflicting_reqs: set[tuple[str, str]]
) -> None:
format_string = (
"Some build dependencies for {requirement} "
@@ -148,7 +155,7 @@ class SourceDistribution(AbstractDistribution):
)
raise InstallationError(error_message)
- def _raise_missing_reqs(self, missing: Set[str]) -> None:
+ def _raise_missing_reqs(self, missing: set[str]) -> None:
format_string = (
"Some build dependencies for {requirement} are missing: {missing}."
)
diff --git a/contrib/python/pip/pip/_internal/distributions/wheel.py b/contrib/python/pip/pip/_internal/distributions/wheel.py
index bfadd39dcb7..ee12bfadc2e 100644
--- a/contrib/python/pip/pip/_internal/distributions/wheel.py
+++ b/contrib/python/pip/pip/_internal/distributions/wheel.py
@@ -1,4 +1,6 @@
-from typing import TYPE_CHECKING, Optional
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from pip._vendor.packaging.utils import canonicalize_name
@@ -10,7 +12,7 @@ from pip._internal.metadata import (
)
if TYPE_CHECKING:
- from pip._internal.index.package_finder import PackageFinder
+ from pip._internal.build_env import BuildEnvironmentInstaller
class WheelDistribution(AbstractDistribution):
@@ -20,7 +22,7 @@ class WheelDistribution(AbstractDistribution):
"""
@property
- def build_tracker_id(self) -> Optional[str]:
+ def build_tracker_id(self) -> str | None:
return None
def get_metadata_distribution(self) -> BaseDistribution:
@@ -35,7 +37,7 @@ class WheelDistribution(AbstractDistribution):
def prepare_distribution_metadata(
self,
- finder: "PackageFinder",
+ build_env_installer: BuildEnvironmentInstaller,
build_isolation: bool,
check_build_deps: bool,
) -> None:
diff --git a/contrib/python/pip/pip/_internal/exceptions.py b/contrib/python/pip/pip/_internal/exceptions.py
index 4fe4aadef2f..98f95494c62 100644
--- a/contrib/python/pip/pip/_internal/exceptions.py
+++ b/contrib/python/pip/pip/_internal/exceptions.py
@@ -5,6 +5,8 @@ operate. This is expected to be importable from any/all files within the
subpackage and, thus, should not depend on them.
"""
+from __future__ import annotations
+
import configparser
import contextlib
import locale
@@ -12,8 +14,9 @@ import logging
import pathlib
import re
import sys
+from collections.abc import Iterator
from itertools import chain, groupby, repeat
-from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union
+from typing import TYPE_CHECKING, Literal
from pip._vendor.packaging.requirements import InvalidRequirement
from pip._vendor.packaging.version import InvalidVersion
@@ -27,7 +30,7 @@ if TYPE_CHECKING:
from pip._vendor.requests.models import Request, Response
from pip._internal.metadata import BaseDistribution
- from pip._internal.models.link import Link
+ from pip._internal.network.download import _FileDownload
from pip._internal.req.req_install import InstallRequirement
logger = logging.getLogger(__name__)
@@ -41,7 +44,7 @@ def _is_kebab_case(s: str) -> bool:
def _prefix_with_indent(
- s: Union[Text, str],
+ s: Text | str,
console: Console,
*,
prefix: str,
@@ -77,13 +80,13 @@ class DiagnosticPipError(PipError):
def __init__(
self,
*,
- kind: 'Literal["error", "warning"]' = "error",
- reference: Optional[str] = None,
- message: Union[str, Text],
- context: Optional[Union[str, Text]],
- hint_stmt: Optional[Union[str, Text]],
- note_stmt: Optional[Union[str, Text]] = None,
- link: Optional[str] = None,
+ kind: Literal["error", "warning"] = "error",
+ reference: str | None = None,
+ message: str | Text,
+ context: str | Text | None,
+ hint_stmt: str | Text | None,
+ note_stmt: str | Text | None = None,
+ link: str | None = None,
) -> None:
# Ensure a proper reference is provided.
if reference is None:
@@ -232,7 +235,7 @@ class NoneMetadataError(PipError):
def __init__(
self,
- dist: "BaseDistribution",
+ dist: BaseDistribution,
metadata_name: str,
) -> None:
"""
@@ -293,8 +296,8 @@ class NetworkConnectionError(PipError):
def __init__(
self,
error_msg: str,
- response: Optional["Response"] = None,
- request: Optional["Request"] = None,
+ response: Response | None = None,
+ request: Request | None = None,
) -> None:
"""
Initialize NetworkConnectionError with `request` and `response`
@@ -343,7 +346,7 @@ class MetadataInconsistent(InstallationError):
"""
def __init__(
- self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
+ self, ireq: InstallRequirement, field: str, f_val: str, m_val: str
) -> None:
self.ireq = ireq
self.field = field
@@ -360,7 +363,7 @@ class MetadataInconsistent(InstallationError):
class MetadataInvalid(InstallationError):
"""Metadata is invalid."""
- def __init__(self, ireq: "InstallRequirement", error: str) -> None:
+ def __init__(self, ireq: InstallRequirement, error: str) -> None:
self.ireq = ireq
self.error = error
@@ -378,7 +381,7 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
*,
command_description: str,
exit_code: int,
- output_lines: Optional[List[str]],
+ output_lines: list[str] | None,
) -> None:
if output_lines is None:
output_prompt = Text("See above for output.")
@@ -432,9 +435,9 @@ class HashErrors(InstallationError):
"""Multiple HashError instances rolled into one for reporting"""
def __init__(self) -> None:
- self.errors: List[HashError] = []
+ self.errors: list[HashError] = []
- def append(self, error: "HashError") -> None:
+ def append(self, error: HashError) -> None:
self.errors.append(error)
def __str__(self) -> str:
@@ -468,7 +471,7 @@ class HashError(InstallationError):
"""
- req: Optional["InstallRequirement"] = None
+ req: InstallRequirement | None = None
head = ""
order: int = -1
@@ -590,7 +593,7 @@ class HashMismatch(HashError):
"someone may have tampered with them."
)
- def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
+ def __init__(self, allowed: dict[str, list[str]], gots: dict[str, _Hash]) -> None:
"""
:param allowed: A dict of algorithm names pointing to lists of allowed
hex digests
@@ -615,12 +618,12 @@ class HashMismatch(HashError):
"""
- def hash_then_or(hash_name: str) -> "chain[str]":
+ def hash_then_or(hash_name: str) -> chain[str]:
# For now, all the decent hashes have 6-char names, so we can get
# away with hard-coding space literals.
return chain([hash_name], repeat(" or"))
- lines: List[str] = []
+ lines: list[str] = []
for hash_name, expecteds in self.allowed.items():
prefix = hash_then_or(hash_name)
lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)
@@ -641,8 +644,8 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
def __init__(
self,
reason: str = "could not be loaded",
- fname: Optional[str] = None,
- error: Optional[configparser.Error] = None,
+ fname: str | None = None,
+ error: configparser.Error | None = None,
) -> None:
super().__init__(error)
self.reason = reason
@@ -677,7 +680,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
reference = "externally-managed-environment"
- def __init__(self, error: Optional[str]) -> None:
+ def __init__(self, error: str | None) -> None:
if error is None:
context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
else:
@@ -704,7 +707,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
try:
category = locale.LC_MESSAGES
except AttributeError:
- lang: Optional[str] = None
+ lang: str | None = None
else:
lang, _ = locale.getlocale(category)
if lang is not None:
@@ -719,8 +722,8 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
@classmethod
def from_config(
cls,
- config: Union[pathlib.Path, str],
- ) -> "ExternallyManagedEnvironment":
+ config: pathlib.Path | str,
+ ) -> ExternallyManagedEnvironment:
parser = configparser.ConfigParser(interpolation=None)
try:
parser.read(config, encoding="utf-8")
@@ -741,7 +744,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
class UninstallMissingRecord(DiagnosticPipError):
reference = "uninstall-no-record-file"
- def __init__(self, *, distribution: "BaseDistribution") -> None:
+ def __init__(self, *, distribution: BaseDistribution) -> None:
installer = distribution.installer
if not installer or installer == "pip":
dep = f"{distribution.raw_name}=={distribution.version}"
@@ -768,7 +771,7 @@ class UninstallMissingRecord(DiagnosticPipError):
class LegacyDistutilsInstall(DiagnosticPipError):
reference = "uninstall-distutils-installed-package"
- def __init__(self, *, distribution: "BaseDistribution") -> None:
+ def __init__(self, *, distribution: BaseDistribution) -> None:
super().__init__(
message=Text(f"Cannot uninstall {distribution}"),
context=(
@@ -786,8 +789,8 @@ class InvalidInstalledPackage(DiagnosticPipError):
def __init__(
self,
*,
- dist: "BaseDistribution",
- invalid_exc: Union[InvalidRequirement, InvalidVersion],
+ dist: BaseDistribution,
+ invalid_exc: InvalidRequirement | InvalidVersion,
) -> None:
installed_location = dist.installed_location
@@ -816,17 +819,19 @@ class IncompleteDownloadError(DiagnosticPipError):
reference = "incomplete-download"
- def __init__(
- self, link: "Link", received: int, expected: int, *, retries: int
- ) -> None:
+ def __init__(self, download: _FileDownload) -> None:
# Dodge circular import.
from pip._internal.utils.misc import format_size
- download_status = f"{format_size(received)}/{format_size(expected)}"
- if retries:
- retry_status = f"after {retries} attempts "
+ assert download.size is not None
+ download_status = (
+ f"{format_size(download.bytes_received)}/{format_size(download.size)}"
+ )
+ if download.reattempts:
+ retry_status = f"after {download.reattempts + 1} attempts "
hint = "Use --resume-retries to configure resume attempt limit."
else:
+ # Download retrying is not enabled.
retry_status = ""
hint = "Consider using --resume-retries to enable download resumption."
message = Text(
@@ -836,7 +841,7 @@ class IncompleteDownloadError(DiagnosticPipError):
super().__init__(
message=message,
- context=f"URL: {link.redacted_url}",
+ context=f"URL: {download.link.redacted_url}",
hint_stmt=hint,
note_stmt="This is an issue with network connectivity, not pip.",
)
@@ -860,3 +865,17 @@ class ResolutionTooDeepError(DiagnosticPipError):
),
link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors",
)
+
+
+class InstallWheelBuildError(DiagnosticPipError):
+ reference = "failed-wheel-build-for-install"
+
+ def __init__(self, failed: list[InstallRequirement]) -> None:
+ super().__init__(
+ message=(
+ "Failed to build installable wheels for some "
+ "pyproject.toml based projects"
+ ),
+ context=", ".join(r.name for r in failed), # type: ignore
+ hint_stmt=None,
+ )
diff --git a/contrib/python/pip/pip/_internal/index/collector.py b/contrib/python/pip/pip/_internal/index/collector.py
index 5f8fdee3d46..00d66daa3bf 100644
--- a/contrib/python/pip/pip/_internal/index/collector.py
+++ b/contrib/python/pip/pip/_internal/index/collector.py
@@ -2,6 +2,8 @@
The main purpose of this module is to expose LinkCollector.collect_sources().
"""
+from __future__ import annotations
+
import collections
import email.message
import functools
@@ -11,21 +13,14 @@ import logging
import os
import urllib.parse
import urllib.request
+from collections.abc import Iterable, MutableMapping, Sequence
from dataclasses import dataclass
from html.parser import HTMLParser
from optparse import Values
from typing import (
Callable,
- Dict,
- Iterable,
- List,
- MutableMapping,
NamedTuple,
- Optional,
Protocol,
- Sequence,
- Tuple,
- Union,
)
from pip._vendor import requests
@@ -48,7 +43,7 @@ logger = logging.getLogger(__name__)
ResponseHeaders = MutableMapping[str, str]
-def _match_vcs_scheme(url: str) -> Optional[str]:
+def _match_vcs_scheme(url: str) -> str | None:
"""Look for VCS schemes in the URL.
Returns the matched VCS scheme, or None if there's no match.
@@ -173,7 +168,7 @@ def _get_simple_response(url: str, session: PipSession) -> Response:
return resp
-def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
+def _get_encoding_from_headers(headers: ResponseHeaders) -> str | None:
"""Determine if we have any encoding information in our headers."""
if headers and "Content-Type" in headers:
m = email.message.Message()
@@ -185,7 +180,7 @@ def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
class CacheablePageContent:
- def __init__(self, page: "IndexContent") -> None:
+ def __init__(self, page: IndexContent) -> None:
assert page.cache_link_parsing
self.page = page
@@ -197,7 +192,7 @@ class CacheablePageContent:
class ParseLinks(Protocol):
- def __call__(self, page: "IndexContent") -> Iterable[Link]: ...
+ def __call__(self, page: IndexContent) -> Iterable[Link]: ...
def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
@@ -207,12 +202,12 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
`page` has `page.cache_link_parsing == False`.
"""
- @functools.lru_cache(maxsize=None)
- def wrapper(cacheable_page: CacheablePageContent) -> List[Link]:
+ @functools.cache
+ def wrapper(cacheable_page: CacheablePageContent) -> list[Link]:
return list(fn(cacheable_page.page))
@functools.wraps(fn)
- def wrapper_wrapper(page: "IndexContent") -> List[Link]:
+ def wrapper_wrapper(page: IndexContent) -> list[Link]:
if page.cache_link_parsing:
return wrapper(CacheablePageContent(page))
return list(fn(page))
@@ -221,7 +216,7 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
@with_cached_index_content
-def parse_links(page: "IndexContent") -> Iterable[Link]:
+def parse_links(page: IndexContent) -> Iterable[Link]:
"""
Parse a Simple API's Index Content, and yield its anchor elements as Link objects.
"""
@@ -262,7 +257,7 @@ class IndexContent:
content: bytes
content_type: str
- encoding: Optional[str]
+ encoding: str | None
url: str
cache_link_parsing: bool = True
@@ -280,10 +275,10 @@ class HTMLLinkParser(HTMLParser):
super().__init__(convert_charrefs=True)
self.url: str = url
- self.base_url: Optional[str] = None
- self.anchors: List[Dict[str, Optional[str]]] = []
+ self.base_url: str | None = None
+ self.anchors: list[dict[str, str | None]] = []
- def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
if tag == "base" and self.base_url is None:
href = self.get_href(attrs)
if href is not None:
@@ -291,7 +286,7 @@ class HTMLLinkParser(HTMLParser):
elif tag == "a":
self.anchors.append(dict(attrs))
- def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
+ def get_href(self, attrs: list[tuple[str, str | None]]) -> str | None:
for name, value in attrs:
if name == "href":
return value
@@ -300,8 +295,8 @@ class HTMLLinkParser(HTMLParser):
def _handle_get_simple_fail(
link: Link,
- reason: Union[str, Exception],
- meth: Optional[Callable[..., None]] = None,
+ reason: str | Exception,
+ meth: Callable[..., None] | None = None,
) -> None:
if meth is None:
meth = logger.debug
@@ -321,7 +316,7 @@ def _make_index_content(
)
-def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]:
+def _get_index_content(link: Link, *, session: PipSession) -> IndexContent | None:
url = link.url.split("#", 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
@@ -383,8 +378,8 @@ def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexCon
class CollectedSources(NamedTuple):
- find_links: Sequence[Optional[LinkSource]]
- index_urls: Sequence[Optional[LinkSource]]
+ find_links: Sequence[LinkSource | None]
+ index_urls: Sequence[LinkSource | None]
class LinkCollector:
@@ -409,7 +404,7 @@ class LinkCollector:
session: PipSession,
options: Values,
suppress_no_index: bool = False,
- ) -> "LinkCollector":
+ ) -> LinkCollector:
"""
:param session: The Session to use to make requests.
:param suppress_no_index: Whether to ignore the --no-index option
@@ -438,10 +433,10 @@ class LinkCollector:
return link_collector
@property
- def find_links(self) -> List[str]:
+ def find_links(self) -> list[str]:
return self.search_scope.find_links
- def fetch_response(self, location: Link) -> Optional[IndexContent]:
+ def fetch_response(self, location: Link) -> IndexContent | None:
"""
Fetch an HTML page containing package links.
"""
diff --git a/contrib/python/pip/pip/_internal/index/package_finder.py b/contrib/python/pip/pip/_internal/index/package_finder.py
index 6971e959d32..bc523cd42d8 100644
--- a/contrib/python/pip/pip/_internal/index/package_finder.py
+++ b/contrib/python/pip/pip/_internal/index/package_finder.py
@@ -1,20 +1,17 @@
"""Routines related to PyPI, indexes"""
+from __future__ import annotations
+
import enum
import functools
import itertools
import logging
import re
+from collections.abc import Iterable
from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
- Dict,
- FrozenSet,
- Iterable,
- List,
Optional,
- Set,
- Tuple,
Union,
)
@@ -48,20 +45,20 @@ from pip._internal.utils.packaging import check_requires_python
from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
if TYPE_CHECKING:
- from pip._vendor.typing_extensions import TypeGuard
+ from typing_extensions import TypeGuard
__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
logger = getLogger(__name__)
-BuildTag = Union[Tuple[()], Tuple[int, str]]
-CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
+BuildTag = Union[tuple[()], tuple[int, str]]
+CandidateSortingKey = tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
def _check_link_requires_python(
link: Link,
- version_info: Tuple[int, int, int],
+ version_info: tuple[int, int, int],
ignore_requires_python: bool = False,
) -> bool:
"""
@@ -131,10 +128,10 @@ class LinkEvaluator:
self,
project_name: str,
canonical_name: str,
- formats: FrozenSet[str],
+ formats: frozenset[str],
target_python: TargetPython,
allow_yanked: bool,
- ignore_requires_python: Optional[bool] = None,
+ ignore_requires_python: bool | None = None,
) -> None:
"""
:param project_name: The user supplied package name.
@@ -164,7 +161,7 @@ class LinkEvaluator:
self.project_name = project_name
- def evaluate_link(self, link: Link) -> Tuple[LinkType, str]:
+ def evaluate_link(self, link: Link) -> tuple[LinkType, str]:
"""
Determine whether a link is a candidate for installation.
@@ -251,7 +248,19 @@ class LinkEvaluator:
ignore_requires_python=self._ignore_requires_python,
)
if not supports_python:
- reason = f"{version} Requires-Python {link.requires_python}"
+ requires_python = link.requires_python
+ if requires_python:
+
+ def get_version_sort_key(v: str) -> tuple[int, ...]:
+ return tuple(int(s) for s in v.split(".") if s.isdigit())
+
+ requires_python = ",".join(
+ sorted(
+ (str(s) for s in specifiers.SpecifierSet(requires_python)),
+ key=get_version_sort_key,
+ )
+ )
+ reason = f"{version} Requires-Python {requires_python}"
return (LinkType.requires_python_mismatch, reason)
logger.debug("Found link %s, version: %s", link, version)
@@ -260,10 +269,10 @@ class LinkEvaluator:
def filter_unallowed_hashes(
- candidates: List[InstallationCandidate],
- hashes: Optional[Hashes],
+ candidates: list[InstallationCandidate],
+ hashes: Hashes | None,
project_name: str,
-) -> List[InstallationCandidate]:
+) -> list[InstallationCandidate]:
"""
Filter out candidates whose hashes aren't allowed, and return a new
list of candidates.
@@ -357,9 +366,9 @@ class BestCandidateResult:
if no applicable candidates were found.
"""
- all_candidates: List[InstallationCandidate]
- applicable_candidates: List[InstallationCandidate]
- best_candidate: Optional[InstallationCandidate]
+ all_candidates: list[InstallationCandidate]
+ applicable_candidates: list[InstallationCandidate]
+ best_candidate: InstallationCandidate | None
def __post_init__(self) -> None:
assert set(self.applicable_candidates) <= set(self.all_candidates)
@@ -380,12 +389,12 @@ class CandidateEvaluator:
def create(
cls,
project_name: str,
- target_python: Optional[TargetPython] = None,
+ target_python: TargetPython | None = None,
prefer_binary: bool = False,
allow_all_prereleases: bool = False,
- specifier: Optional[specifiers.BaseSpecifier] = None,
- hashes: Optional[Hashes] = None,
- ) -> "CandidateEvaluator":
+ specifier: specifiers.BaseSpecifier | None = None,
+ hashes: Hashes | None = None,
+ ) -> CandidateEvaluator:
"""Create a CandidateEvaluator object.
:param target_python: The target Python interpreter to use when
@@ -415,11 +424,11 @@ class CandidateEvaluator:
def __init__(
self,
project_name: str,
- supported_tags: List[Tag],
+ supported_tags: list[Tag],
specifier: specifiers.BaseSpecifier,
prefer_binary: bool = False,
allow_all_prereleases: bool = False,
- hashes: Optional[Hashes] = None,
+ hashes: Hashes | None = None,
) -> None:
"""
:param supported_tags: The PEP 425 tags supported by the target
@@ -440,8 +449,8 @@ class CandidateEvaluator:
def get_applicable_candidates(
self,
- candidates: List[InstallationCandidate],
- ) -> List[InstallationCandidate]:
+ candidates: list[InstallationCandidate],
+ ) -> list[InstallationCandidate]:
"""
Return the applicable candidates from a list of candidates.
"""
@@ -540,8 +549,8 @@ class CandidateEvaluator:
def sort_best_candidate(
self,
- candidates: List[InstallationCandidate],
- ) -> Optional[InstallationCandidate]:
+ candidates: list[InstallationCandidate],
+ ) -> InstallationCandidate | None:
"""
Return the best candidate per the instance's sort order, or None if
no candidate is acceptable.
@@ -553,7 +562,7 @@ class CandidateEvaluator:
def compute_best_candidate(
self,
- candidates: List[InstallationCandidate],
+ candidates: list[InstallationCandidate],
) -> BestCandidateResult:
"""
Compute and return a `BestCandidateResult` instance.
@@ -581,9 +590,9 @@ class PackageFinder:
link_collector: LinkCollector,
target_python: TargetPython,
allow_yanked: bool,
- format_control: Optional[FormatControl] = None,
- candidate_prefs: Optional[CandidatePreferences] = None,
- ignore_requires_python: Optional[bool] = None,
+ format_control: FormatControl | None = None,
+ candidate_prefs: CandidatePreferences | None = None,
+ ignore_requires_python: bool | None = None,
) -> None:
"""
This constructor is primarily meant to be used by the create() class
@@ -609,12 +618,12 @@ class PackageFinder:
self.format_control = format_control
# These are boring links that have already been logged somehow.
- self._logged_links: Set[Tuple[Link, LinkType, str]] = set()
+ self._logged_links: set[tuple[Link, LinkType, str]] = set()
# Cache of the result of finding candidates
- self._all_candidates: Dict[str, List[InstallationCandidate]] = {}
- self._best_candidates: Dict[
- Tuple[str, Optional[specifiers.BaseSpecifier], Optional[Hashes]],
+ self._all_candidates: dict[str, list[InstallationCandidate]] = {}
+ self._best_candidates: dict[
+ tuple[str, specifiers.BaseSpecifier | None, Hashes | None],
BestCandidateResult,
] = {}
@@ -627,8 +636,8 @@ class PackageFinder:
cls,
link_collector: LinkCollector,
selection_prefs: SelectionPreferences,
- target_python: Optional[TargetPython] = None,
- ) -> "PackageFinder":
+ target_python: TargetPython | None = None,
+ ) -> PackageFinder:
"""Create a PackageFinder.
:param selection_prefs: The candidate selection preferences, as a
@@ -667,15 +676,15 @@ class PackageFinder:
self._link_collector.search_scope = search_scope
@property
- def find_links(self) -> List[str]:
+ def find_links(self) -> list[str]:
return self._link_collector.find_links
@property
- def index_urls(self) -> List[str]:
+ def index_urls(self) -> list[str]:
return self.search_scope.index_urls
@property
- def proxy(self) -> Optional[str]:
+ def proxy(self) -> str | None:
return self._link_collector.session.pip_proxy
@property
@@ -684,7 +693,7 @@ class PackageFinder:
yield build_netloc(*host_port)
@property
- def custom_cert(self) -> Optional[str]:
+ def custom_cert(self) -> str | None:
# session.verify is either a boolean (use default bundle/no SSL
# verification) or a string path to a custom CA bundle to use. We only
# care about the latter.
@@ -692,7 +701,7 @@ class PackageFinder:
return verify if isinstance(verify, str) else None
@property
- def client_cert(self) -> Optional[str]:
+ def client_cert(self) -> str | None:
cert = self._link_collector.session.cert
assert not isinstance(cert, tuple), "pip only supports PEM client certs"
return cert
@@ -711,7 +720,7 @@ class PackageFinder:
def set_prefer_binary(self) -> None:
self._candidate_prefs.prefer_binary = True
- def requires_python_skipped_reasons(self) -> List[str]:
+ def requires_python_skipped_reasons(self) -> list[str]:
reasons = {
detail
for _, result, detail in self._logged_links
@@ -732,13 +741,13 @@ class PackageFinder:
ignore_requires_python=self._ignore_requires_python,
)
- def _sort_links(self, links: Iterable[Link]) -> List[Link]:
+ def _sort_links(self, links: Iterable[Link]) -> list[Link]:
"""
Returns elements of links in order, non-egg links first, egg links
second, while eliminating duplicates
"""
eggs, no_eggs = [], []
- seen: Set[Link] = set()
+ seen: set[Link] = set()
for link in links:
if link not in seen:
seen.add(link)
@@ -758,7 +767,7 @@ class PackageFinder:
def get_install_candidate(
self, link_evaluator: LinkEvaluator, link: Link
- ) -> Optional[InstallationCandidate]:
+ ) -> InstallationCandidate | None:
"""
If the link is a candidate for install, convert it to an
InstallationCandidate and return it. Otherwise, return None.
@@ -779,7 +788,7 @@ class PackageFinder:
def evaluate_links(
self, link_evaluator: LinkEvaluator, links: Iterable[Link]
- ) -> List[InstallationCandidate]:
+ ) -> list[InstallationCandidate]:
"""
Convert links that are candidates to InstallationCandidate objects.
"""
@@ -793,7 +802,7 @@ class PackageFinder:
def process_project_url(
self, project_url: Link, link_evaluator: LinkEvaluator
- ) -> List[InstallationCandidate]:
+ ) -> list[InstallationCandidate]:
logger.debug(
"Fetching project page and analyzing links: %s",
project_url,
@@ -812,7 +821,7 @@ class PackageFinder:
return package_links
- def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]:
+ def find_all_candidates(self, project_name: str) -> list[InstallationCandidate]:
"""Find all available InstallationCandidate for project_name
This checks index_urls and find_links.
@@ -872,8 +881,8 @@ class PackageFinder:
def make_candidate_evaluator(
self,
project_name: str,
- specifier: Optional[specifiers.BaseSpecifier] = None,
- hashes: Optional[Hashes] = None,
+ specifier: specifiers.BaseSpecifier | None = None,
+ hashes: Hashes | None = None,
) -> CandidateEvaluator:
"""Create a CandidateEvaluator object to use."""
candidate_prefs = self._candidate_prefs
@@ -889,8 +898,8 @@ class PackageFinder:
def find_best_candidate(
self,
project_name: str,
- specifier: Optional[specifiers.BaseSpecifier] = None,
- hashes: Optional[Hashes] = None,
+ specifier: specifiers.BaseSpecifier | None = None,
+ hashes: Hashes | None = None,
) -> BestCandidateResult:
"""Find matches for the given project and specifier.
@@ -917,7 +926,7 @@ class PackageFinder:
def find_requirement(
self, req: InstallRequirement, upgrade: bool
- ) -> Optional[InstallationCandidate]:
+ ) -> InstallationCandidate | None:
"""Try to find a Link matching req
Expects req, an InstallRequirement and upgrade, a boolean
@@ -935,7 +944,7 @@ class PackageFinder:
)
best_candidate = best_candidate_result.best_candidate
- installed_version: Optional[_BaseVersion] = None
+ installed_version: _BaseVersion | None = None
if req.satisfied_by is not None:
installed_version = req.satisfied_by.version
@@ -965,8 +974,8 @@ class PackageFinder:
raise DistributionNotFound(f"No matching distribution found for {req}")
def _should_install_candidate(
- candidate: Optional[InstallationCandidate],
- ) -> "TypeGuard[InstallationCandidate]":
+ candidate: InstallationCandidate | None,
+ ) -> TypeGuard[InstallationCandidate]:
if installed_version is None:
return True
if best_candidate is None:
@@ -1032,7 +1041,7 @@ def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
raise ValueError(f"{fragment} does not match {canonical_name}")
-def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]:
+def _extract_version_from_fragment(fragment: str, canonical_name: str) -> str | None:
"""Parse the version string from a <package>+<version> filename
"fragment" (stem) or egg fragment.
diff --git a/contrib/python/pip/pip/_internal/index/sources.py b/contrib/python/pip/pip/_internal/index/sources.py
index 3dafb30e6eb..c67c4d73668 100644
--- a/contrib/python/pip/pip/_internal/index/sources.py
+++ b/contrib/python/pip/pip/_internal/index/sources.py
@@ -1,8 +1,11 @@
+from __future__ import annotations
+
import logging
import mimetypes
import os
from collections import defaultdict
-from typing import Callable, Dict, Iterable, List, Optional, Tuple
+from collections.abc import Iterable
+from typing import Callable
from pip._vendor.packaging.utils import (
InvalidSdistFilename,
@@ -27,7 +30,7 @@ PageValidator = Callable[[Link], bool]
class LinkSource:
@property
- def link(self) -> Optional[Link]:
+ def link(self) -> Link | None:
"""Returns the underlying link, if there's one."""
raise NotImplementedError()
@@ -49,8 +52,8 @@ class _FlatDirectoryToUrls:
def __init__(self, path: str) -> None:
self._path = path
- self._page_candidates: List[str] = []
- self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list)
+ self._page_candidates: list[str] = []
+ self._project_name_to_urls: dict[str, list[str]] = defaultdict(list)
self._scanned_directory = False
def _scan_directory(self) -> None:
@@ -77,14 +80,14 @@ class _FlatDirectoryToUrls:
self._scanned_directory = True
@property
- def page_candidates(self) -> List[str]:
+ def page_candidates(self) -> list[str]:
if not self._scanned_directory:
self._scan_directory()
return self._page_candidates
@property
- def project_name_to_urls(self) -> Dict[str, List[str]]:
+ def project_name_to_urls(self) -> dict[str, list[str]]:
if not self._scanned_directory:
self._scan_directory()
@@ -100,7 +103,7 @@ class _FlatDirectorySource(LinkSource):
* ``file_candidates``: Archives in the directory.
"""
- _paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {}
+ _paths_to_urls: dict[str, _FlatDirectoryToUrls] = {}
def __init__(
self,
@@ -119,7 +122,7 @@ class _FlatDirectorySource(LinkSource):
self._paths_to_urls[path] = self._path_to_urls
@property
- def link(self) -> Optional[Link]:
+ def link(self) -> Link | None:
return None
def page_candidates(self) -> FoundCandidates:
@@ -150,7 +153,7 @@ class _LocalFileSource(LinkSource):
self._link = link
@property
- def link(self) -> Optional[Link]:
+ def link(self) -> Link | None:
return self._link
def page_candidates(self) -> FoundCandidates:
@@ -184,7 +187,7 @@ class _RemoteFileSource(LinkSource):
self._link = link
@property
- def link(self) -> Optional[Link]:
+ def link(self) -> Link | None:
return self._link
def page_candidates(self) -> FoundCandidates:
@@ -212,7 +215,7 @@ class _IndexDirectorySource(LinkSource):
self._link = link
@property
- def link(self) -> Optional[Link]:
+ def link(self) -> Link | None:
return self._link
def page_candidates(self) -> FoundCandidates:
@@ -230,9 +233,9 @@ def build_source(
expand_dir: bool,
cache_link_parsing: bool,
project_name: str,
-) -> Tuple[Optional[str], Optional[LinkSource]]:
- path: Optional[str] = None
- url: Optional[str] = None
+) -> tuple[str | None, LinkSource | None]:
+ path: str | None = None
+ url: str | None = None
if os.path.exists(location): # Is a local path.
url = path_to_url(location)
path = location
diff --git a/contrib/python/pip/pip/_internal/locations/__init__.py b/contrib/python/pip/pip/_internal/locations/__init__.py
index dfb5dd36066..9f2c4fe316c 100644
--- a/contrib/python/pip/pip/_internal/locations/__init__.py
+++ b/contrib/python/pip/pip/_internal/locations/__init__.py
@@ -1,10 +1,12 @@
+from __future__ import annotations
+
import functools
import logging
import os
import pathlib
import sys
import sysconfig
-from typing import Any, Dict, Optional
+from typing import Any
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
from pip._internal.utils.compat import WINDOWS
@@ -87,7 +89,7 @@ def _looks_like_bpo_44860() -> bool:
return unix_user_platlib == "$usersite"
-def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
+def _looks_like_red_hat_patched_platlib_purelib(scheme: dict[str, str]) -> bool:
platlib = scheme["platlib"]
if "/$platlibdir/" in platlib:
platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
@@ -97,7 +99,7 @@ def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
[email protected]_cache(maxsize=None)
def _looks_like_red_hat_lib() -> bool:
"""Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
@@ -112,7 +114,7 @@ def _looks_like_red_hat_lib() -> bool:
)
[email protected]_cache(maxsize=None)
def _looks_like_debian_scheme() -> bool:
"""Debian adds two additional schemes."""
from distutils.command.install import INSTALL_SCHEMES
@@ -120,7 +122,7 @@ def _looks_like_debian_scheme() -> bool:
return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
[email protected]_cache(maxsize=None)
def _looks_like_red_hat_scheme() -> bool:
"""Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
@@ -140,7 +142,7 @@ def _looks_like_red_hat_scheme() -> bool:
)
[email protected]_cache(maxsize=None)
def _looks_like_slackware_scheme() -> bool:
"""Slackware patches sysconfig but fails to patch distutils and site.
@@ -156,7 +158,7 @@ def _looks_like_slackware_scheme() -> bool:
return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
[email protected]_cache(maxsize=None)
def _looks_like_msys2_mingw_scheme() -> bool:
"""MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
@@ -174,7 +176,7 @@ def _looks_like_msys2_mingw_scheme() -> bool:
)
[email protected]_cache(maxsize=None)
def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
issue_url = "https://github.com/pypa/pip/issues/10151"
message = (
@@ -192,13 +194,13 @@ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool
return True
[email protected]_cache(maxsize=None)
def _log_context(
*,
user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
- prefix: Optional[str] = None,
+ home: str | None = None,
+ root: str | None = None,
+ prefix: str | None = None,
) -> None:
parts = [
"Additional context:",
@@ -214,10 +216,10 @@ def _log_context(
def get_scheme(
dist_name: str,
user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
+ home: str | None = None,
+ root: str | None = None,
isolated: bool = False,
- prefix: Optional[str] = None,
+ prefix: str | None = None,
) -> Scheme:
new = _sysconfig.get_scheme(
dist_name,
@@ -281,7 +283,7 @@ def get_scheme(
continue
# On Python 3.9+, sysconfig's posix_user scheme sets platlib against
- # sys.platlibdir, but distutils's unix_user incorrectly coninutes
+ # sys.platlibdir, but distutils's unix_user incorrectly continues
# using the same $usersite for both platlib and purelib. This creates a
# mismatch when sys.platlibdir is not "lib".
skip_bpo_44860 = (
diff --git a/contrib/python/pip/pip/_internal/locations/_distutils.py b/contrib/python/pip/pip/_internal/locations/_distutils.py
index 3d856256986..28c066bcee6 100644
--- a/contrib/python/pip/pip/_internal/locations/_distutils.py
+++ b/contrib/python/pip/pip/_internal/locations/_distutils.py
@@ -9,6 +9,8 @@
#
# See https://github.com/pypa/pip/issues/8761 for the original discussion and
# rationale for why this is done within pip.
+from __future__ import annotations
+
try:
__import__("_distutils_hack").remove_shim()
except (ImportError, AttributeError):
@@ -21,7 +23,6 @@ from distutils.cmd import Command as DistutilsCommand
from distutils.command.install import SCHEME_KEYS
from distutils.command.install import install as distutils_install_command
from distutils.sysconfig import get_python_lib
-from typing import Dict, List, Optional, Union
from pip._internal.models.scheme import Scheme
from pip._internal.utils.compat import WINDOWS
@@ -35,19 +36,19 @@ logger = logging.getLogger(__name__)
def distutils_scheme(
dist_name: str,
user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
+ home: str | None = None,
+ root: str | None = None,
isolated: bool = False,
- prefix: Optional[str] = None,
+ prefix: str | None = None,
*,
ignore_config_files: bool = False,
-) -> Dict[str, str]:
+) -> dict[str, str]:
"""
Return a distutils install scheme
"""
from distutils.dist import Distribution
- dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name}
+ dist_args: dict[str, str | list[str]] = {"name": dist_name}
if isolated:
dist_args["script_args"] = ["--no-user-cfg"]
@@ -61,7 +62,7 @@ def distutils_scheme(
"Ignore distutils configs in %s due to encoding errors.",
", ".join(os.path.basename(p) for p in paths),
)
- obj: Optional[DistutilsCommand] = None
+ obj: DistutilsCommand | None = None
obj = d.get_command_obj("install", create=True)
assert obj is not None
i: distutils_install_command = obj
@@ -78,7 +79,7 @@ def distutils_scheme(
i.root = root or i.root
i.finalize_options()
- scheme: Dict[str, str] = {}
+ scheme: dict[str, str] = {}
for key in SCHEME_KEYS:
scheme[key] = getattr(i, "install_" + key)
@@ -115,10 +116,10 @@ def distutils_scheme(
def get_scheme(
dist_name: str,
user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
+ home: str | None = None,
+ root: str | None = None,
isolated: bool = False,
- prefix: Optional[str] = None,
+ prefix: str | None = None,
) -> Scheme:
"""
Get the "scheme" corresponding to the input parameters. The distutils
diff --git a/contrib/python/pip/pip/_internal/locations/_sysconfig.py b/contrib/python/pip/pip/_internal/locations/_sysconfig.py
index ca860ea562c..d4a448ece52 100644
--- a/contrib/python/pip/pip/_internal/locations/_sysconfig.py
+++ b/contrib/python/pip/pip/_internal/locations/_sysconfig.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import logging
import os
import sys
import sysconfig
-import typing
from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
@@ -124,10 +125,10 @@ if sysconfig.get_config_var("userbase") is not None:
def get_scheme(
dist_name: str,
user: bool = False,
- home: typing.Optional[str] = None,
- root: typing.Optional[str] = None,
+ home: str | None = None,
+ root: str | None = None,
isolated: bool = False,
- prefix: typing.Optional[str] = None,
+ prefix: str | None = None,
) -> Scheme:
"""
Get the "scheme" corresponding to the input parameters.
diff --git a/contrib/python/pip/pip/_internal/locations/base.py b/contrib/python/pip/pip/_internal/locations/base.py
index 3f9f896e632..17cd0e87591 100644
--- a/contrib/python/pip/pip/_internal/locations/base.py
+++ b/contrib/python/pip/pip/_internal/locations/base.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import functools
import os
import site
import sys
import sysconfig
-import typing
from pip._internal.exceptions import InstallationError
from pip._internal.utils import appdirs
@@ -71,11 +72,11 @@ def get_src_prefix() -> str:
try:
# Use getusersitepackages if this is present, as it ensures that the
# value is initialised properly.
- user_site: typing.Optional[str] = site.getusersitepackages()
+ user_site: str | None = site.getusersitepackages()
except AttributeError:
user_site = site.USER_SITE
[email protected]_cache(maxsize=None)
def is_osx_framework() -> bool:
return bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
diff --git a/contrib/python/pip/pip/_internal/main.py b/contrib/python/pip/pip/_internal/main.py
index 33c6d24cd85..ec52c4e0c39 100644
--- a/contrib/python/pip/pip/_internal/main.py
+++ b/contrib/python/pip/pip/_internal/main.py
@@ -1,7 +1,7 @@
-from typing import List, Optional
+from __future__ import annotations
-def main(args: Optional[List[str]] = None) -> int:
+def main(args: list[str] | None = None) -> int:
"""This is preserved for old console scripts that may still be referencing
it.
diff --git a/contrib/python/pip/pip/_internal/metadata/__init__.py b/contrib/python/pip/pip/_internal/metadata/__init__.py
index 60b62b956bd..927e375cad0 100644
--- a/contrib/python/pip/pip/_internal/metadata/__init__.py
+++ b/contrib/python/pip/pip/_internal/metadata/__init__.py
@@ -1,8 +1,10 @@
+from __future__ import annotations
+
import contextlib
import functools
import os
import sys
-from typing import List, Literal, Optional, Protocol, Type, cast
+from typing import Literal, Protocol, cast
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import strtobool
@@ -81,12 +83,12 @@ def _emit_pkg_resources_deprecation_if_needed() -> None:
class Backend(Protocol):
- NAME: 'Literal["importlib", "pkg_resources"]'
- Distribution: Type[BaseDistribution]
- Environment: Type[BaseEnvironment]
+ NAME: Literal["importlib", "pkg_resources"]
+ Distribution: type[BaseDistribution]
+ Environment: type[BaseEnvironment]
[email protected]_cache(maxsize=None)
def select_backend() -> Backend:
if _should_use_importlib_metadata():
from . import importlib
@@ -110,7 +112,7 @@ def get_default_environment() -> BaseEnvironment:
return select_backend().Environment.default()
-def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
+def get_environment(paths: list[str] | None) -> BaseEnvironment:
"""Get a representation of the environment specified by ``paths``.
This returns an Environment instance from the chosen backend based on the
diff --git a/contrib/python/pip/pip/_internal/metadata/_json.py b/contrib/python/pip/pip/_internal/metadata/_json.py
index f3aeab3225f..b39ac054578 100644
--- a/contrib/python/pip/pip/_internal/metadata/_json.py
+++ b/contrib/python/pip/pip/_internal/metadata/_json.py
@@ -1,8 +1,9 @@
# Extracted from https://github.com/pfmoore/pkg_metadata
+from __future__ import annotations
from email.header import Header, decode_header, make_header
from email.message import Message
-from typing import Any, Dict, List, Union, cast
+from typing import Any, cast
METADATA_FIELDS = [
# Name, Multiple-Use
@@ -40,10 +41,10 @@ def json_name(field: str) -> str:
return field.lower().replace("-", "_")
-def msg_to_json(msg: Message) -> Dict[str, Any]:
+def msg_to_json(msg: Message) -> dict[str, Any]:
"""Convert a Message object into a JSON-compatible dictionary."""
- def sanitise_header(h: Union[Header, str]) -> str:
+ def sanitise_header(h: Header | str) -> str:
if isinstance(h, Header):
chunks = []
for bytes, encoding in decode_header(h):
@@ -65,7 +66,7 @@ def msg_to_json(msg: Message) -> Dict[str, Any]:
continue
key = json_name(field)
if multi:
- value: Union[str, List[str]] = [
+ value: str | list[str] = [
sanitise_header(v) for v in msg.get_all(field) # type: ignore
]
else:
diff --git a/contrib/python/pip/pip/_internal/metadata/base.py b/contrib/python/pip/pip/_internal/metadata/base.py
index ea5a0756dd7..230e11473c6 100644
--- a/contrib/python/pip/pip/_internal/metadata/base.py
+++ b/contrib/python/pip/pip/_internal/metadata/base.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import csv
import email.message
import functools
@@ -6,19 +8,12 @@ import logging
import pathlib
import re
import zipfile
+from collections.abc import Collection, Container, Iterable, Iterator
from typing import (
IO,
Any,
- Collection,
- Container,
- Dict,
- Iterable,
- Iterator,
- List,
NamedTuple,
- Optional,
Protocol,
- Tuple,
Union,
)
@@ -61,8 +56,8 @@ class BaseEntryPoint(Protocol):
def _convert_installed_files_path(
- entry: Tuple[str, ...],
- info: Tuple[str, ...],
+ entry: tuple[str, ...],
+ info: tuple[str, ...],
) -> str:
"""Convert a legacy installed-files.txt path into modern RECORD path.
@@ -98,7 +93,7 @@ class RequiresEntry(NamedTuple):
class BaseDistribution(Protocol):
@classmethod
- def from_directory(cls, directory: str) -> "BaseDistribution":
+ def from_directory(cls, directory: str) -> BaseDistribution:
"""Load the distribution from a metadata directory.
:param directory: Path to a metadata directory, e.g. ``.dist-info``.
@@ -111,7 +106,7 @@ class BaseDistribution(Protocol):
metadata_contents: bytes,
filename: str,
project_name: str,
- ) -> "BaseDistribution":
+ ) -> BaseDistribution:
"""Load the distribution from the contents of a METADATA file.
This is used to implement PEP 658 by generating a "shallow" dist object that can
@@ -124,7 +119,7 @@ class BaseDistribution(Protocol):
raise NotImplementedError()
@classmethod
- def from_wheel(cls, wheel: "Wheel", name: str) -> "BaseDistribution":
+ def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
"""Load the distribution from a given wheel.
:param wheel: A concrete wheel definition.
@@ -144,7 +139,7 @@ class BaseDistribution(Protocol):
return f"{self.raw_name} {self.raw_version}"
@property
- def location(self) -> Optional[str]:
+ def location(self) -> str | None:
"""Where the distribution is loaded from.
A string value is not necessarily a filesystem path, since distributions
@@ -158,7 +153,7 @@ class BaseDistribution(Protocol):
raise NotImplementedError()
@property
- def editable_project_location(self) -> Optional[str]:
+ def editable_project_location(self) -> str | None:
"""The project location for editable distributions.
This is the directory where pyproject.toml or setup.py is located.
@@ -180,7 +175,7 @@ class BaseDistribution(Protocol):
return None
@property
- def installed_location(self) -> Optional[str]:
+ def installed_location(self) -> str | None:
"""The distribution's "installed" location.
This should generally be a ``site-packages`` directory. This is
@@ -193,7 +188,7 @@ class BaseDistribution(Protocol):
raise NotImplementedError()
@property
- def info_location(self) -> Optional[str]:
+ def info_location(self) -> str | None:
"""Location of the .[egg|dist]-info directory or file.
Similarly to ``location``, a string value is not necessarily a
@@ -290,7 +285,7 @@ class BaseDistribution(Protocol):
return self.raw_name.replace("-", "_")
@property
- def direct_url(self) -> Optional[DirectUrl]:
+ def direct_url(self) -> DirectUrl | None:
"""Obtain a DirectUrl from this distribution.
Returns None if the distribution has no `direct_url.json` metadata,
@@ -398,7 +393,7 @@ class BaseDistribution(Protocol):
return metadata
@property
- def metadata_dict(self) -> Dict[str, Any]:
+ def metadata_dict(self) -> dict[str, Any]:
"""PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO.
This should return an empty dict if the metadata file is unavailable.
@@ -409,7 +404,7 @@ class BaseDistribution(Protocol):
return msg_to_json(self.metadata)
@property
- def metadata_version(self) -> Optional[str]:
+ def metadata_version(self) -> str | None:
"""Value of "Metadata-Version:" in distribution metadata, if available."""
return self.metadata.get("Metadata-Version")
@@ -463,7 +458,7 @@ class BaseDistribution(Protocol):
"""
raise NotImplementedError()
- def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
+ def _iter_declared_entries_from_record(self) -> Iterator[str] | None:
try:
text = self.read_text("RECORD")
except FileNotFoundError:
@@ -471,7 +466,7 @@ class BaseDistribution(Protocol):
# This extra Path-str cast normalizes entries.
return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
- def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
+ def _iter_declared_entries_from_legacy(self) -> Iterator[str] | None:
try:
text = self.read_text("installed-files.txt")
except FileNotFoundError:
@@ -492,7 +487,7 @@ class BaseDistribution(Protocol):
for p in paths
)
- def iter_declared_entries(self) -> Optional[Iterator[str]]:
+ def iter_declared_entries(self) -> Iterator[str] | None:
"""Iterate through file entries declared in this distribution.
For modern .dist-info distributions, this is the files listed in the
@@ -585,14 +580,14 @@ class BaseEnvironment:
"""An environment containing distributions to introspect."""
@classmethod
- def default(cls) -> "BaseEnvironment":
+ def default(cls) -> BaseEnvironment:
raise NotImplementedError()
@classmethod
- def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment":
+ def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
raise NotImplementedError()
- def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
+ def get_distribution(self, name: str) -> BaseDistribution | None:
"""Given a requirement name, return the installed distributions.
The name may not be normalized. The implementation must canonicalize
@@ -600,7 +595,7 @@ class BaseEnvironment:
"""
raise NotImplementedError()
- def _iter_distributions(self) -> Iterator["BaseDistribution"]:
+ def _iter_distributions(self) -> Iterator[BaseDistribution]:
"""Iterate through installed distributions.
This function should be implemented by subclass, but never called
diff --git a/contrib/python/pip/pip/_internal/metadata/importlib/_compat.py b/contrib/python/pip/pip/_internal/metadata/importlib/_compat.py
index ec1e815cdbd..7de614d7f64 100644
--- a/contrib/python/pip/pip/_internal/metadata/importlib/_compat.py
+++ b/contrib/python/pip/pip/_internal/metadata/importlib/_compat.py
@@ -1,6 +1,8 @@
+from __future__ import annotations
+
import importlib.metadata
import os
-from typing import Any, Optional, Protocol, Tuple, cast
+from typing import Any, Protocol, cast
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
@@ -30,11 +32,11 @@ class BasePath(Protocol):
raise NotImplementedError()
@property
- def parent(self) -> "BasePath":
+ def parent(self) -> BasePath:
raise NotImplementedError()
-def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]:
+def get_info_location(d: importlib.metadata.Distribution) -> BasePath | None:
"""Find the path to the distribution's metadata directory.
HACK: This relies on importlib.metadata's private ``_path`` attribute. Not
@@ -48,7 +50,7 @@ def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]:
def parse_name_and_version_from_info_directory(
dist: importlib.metadata.Distribution,
-) -> Tuple[Optional[str], Optional[str]]:
+) -> tuple[str | None, str | None]:
"""Get a name and version from the metadata directory name.
This is much faster than reading distribution metadata.
diff --git a/contrib/python/pip/pip/_internal/metadata/importlib/_dists.py b/contrib/python/pip/pip/_internal/metadata/importlib/_dists.py
index d220b616e28..97363af9a55 100644
--- a/contrib/python/pip/pip/_internal/metadata/importlib/_dists.py
+++ b/contrib/python/pip/pip/_internal/metadata/importlib/_dists.py
@@ -1,17 +1,12 @@
+from __future__ import annotations
+
import email.message
import importlib.metadata
import pathlib
import zipfile
+from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence
from os import PathLike
from typing import (
- Collection,
- Dict,
- Iterable,
- Iterator,
- Mapping,
- Optional,
- Sequence,
- Union,
cast,
)
@@ -64,7 +59,7 @@ class WheelDistribution(importlib.metadata.Distribution):
zf: zipfile.ZipFile,
name: str,
location: str,
- ) -> "WheelDistribution":
+ ) -> WheelDistribution:
info_dir, _ = parse_wheel(zf, name)
paths = (
(name, pathlib.PurePosixPath(name.split("/", 1)[-1]))
@@ -84,7 +79,7 @@ class WheelDistribution(importlib.metadata.Distribution):
return iter(self._files)
raise FileNotFoundError(path)
- def read_text(self, filename: str) -> Optional[str]:
+ def read_text(self, filename: str) -> str | None:
try:
data = self._files[pathlib.PurePosixPath(filename)]
except KeyError:
@@ -97,7 +92,7 @@ class WheelDistribution(importlib.metadata.Distribution):
raise UnsupportedWheel(error)
return text
- def locate_file(self, path: Union[str, "PathLike[str]"]) -> pathlib.Path:
+ def locate_file(self, path: str | PathLike[str]) -> pathlib.Path:
# This method doesn't make sense for our in-memory wheel, but the API
# requires us to define it.
raise NotImplementedError
@@ -107,8 +102,8 @@ class Distribution(BaseDistribution):
def __init__(
self,
dist: importlib.metadata.Distribution,
- info_location: Optional[BasePath],
- installed_location: Optional[BasePath],
+ info_location: BasePath | None,
+ installed_location: BasePath | None,
) -> None:
self._dist = dist
self._info_location = info_location
@@ -147,19 +142,19 @@ class Distribution(BaseDistribution):
return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))
@property
- def location(self) -> Optional[str]:
+ def location(self) -> str | None:
if self._info_location is None:
return None
return str(self._info_location.parent)
@property
- def info_location(self) -> Optional[str]:
+ def info_location(self) -> str | None:
if self._info_location is None:
return None
return str(self._info_location)
@property
- def installed_location(self) -> Optional[str]:
+ def installed_location(self) -> str | None:
if self._installed_location is None:
return None
return normalize_path(str(self._installed_location))
@@ -215,7 +210,7 @@ class Distribution(BaseDistribution):
]
def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
- contexts: Sequence[Dict[str, str]] = [{"extra": e} for e in extras]
+ contexts: Sequence[dict[str, str]] = [{"extra": e} for e in extras]
for req_string in self.metadata.get_all("Requires-Dist", []):
# strip() because email.message.Message.get_all() may return a leading \n
# in case a long header was wrapped.
diff --git a/contrib/python/pip/pip/_internal/metadata/importlib/_envs.py b/contrib/python/pip/pip/_internal/metadata/importlib/_envs.py
index 314e75e6731..71a73b7311f 100644
--- a/contrib/python/pip/pip/_internal/metadata/importlib/_envs.py
+++ b/contrib/python/pip/pip/_internal/metadata/importlib/_envs.py
@@ -1,10 +1,13 @@
+from __future__ import annotations
+
import importlib.metadata
import logging
import os
import pathlib
import sys
import zipfile
-from typing import Iterator, List, Optional, Sequence, Set, Tuple
+from collections.abc import Iterator, Sequence
+from typing import Optional
from pip._vendor.packaging.utils import (
InvalidWheelFilename,
@@ -47,10 +50,10 @@ class _DistributionFinder:
installations as well. It's useful feature, after all.
"""
- FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]]
+ FoundResult = tuple[importlib.metadata.Distribution, Optional[BasePath]]
def __init__(self) -> None:
- self._found_names: Set[NormalizedName] = set()
+ self._found_names: set[NormalizedName] = set()
def _find_impl(self, location: str) -> Iterator[FoundResult]:
"""Find distributions in a location."""
@@ -80,7 +83,7 @@ class _DistributionFinder:
"""
for dist, info_location in self._find_impl(location):
if info_location is None:
- installed_location: Optional[BasePath] = None
+ installed_location: BasePath | None = None
else:
installed_location = info_location.parent
yield Distribution(dist, info_location, installed_location)
@@ -119,7 +122,7 @@ class Environment(BaseEnvironment):
return cls(sys.path)
@classmethod
- def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
+ def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
if paths is None:
return cls(sys.path)
return cls(paths)
@@ -130,7 +133,7 @@ class Environment(BaseEnvironment):
yield from finder.find(location)
yield from finder.find_legacy_editables(location)
- def get_distribution(self, name: str) -> Optional[BaseDistribution]:
+ def get_distribution(self, name: str) -> BaseDistribution | None:
canonical_name = canonicalize_name(name)
matches = (
distribution
diff --git a/contrib/python/pip/pip/_internal/metadata/pkg_resources.py b/contrib/python/pip/pip/_internal/metadata/pkg_resources.py
index 4ea84f93a6f..89fce8b6e5d 100644
--- a/contrib/python/pip/pip/_internal/metadata/pkg_resources.py
+++ b/contrib/python/pip/pip/_internal/metadata/pkg_resources.py
@@ -1,16 +1,13 @@
+from __future__ import annotations
+
import email.message
import email.parser
import logging
import os
import zipfile
+from collections.abc import Collection, Iterable, Iterator, Mapping
from typing import (
- Collection,
- Iterable,
- Iterator,
- List,
- Mapping,
NamedTuple,
- Optional,
)
from pip._vendor import pkg_resources
@@ -73,7 +70,7 @@ class InMemoryMetadata:
def metadata_isdir(self, name: str) -> bool:
return False
- def metadata_listdir(self, name: str) -> List[str]:
+ def metadata_listdir(self, name: str) -> list[str]:
return []
def run_script(self, script_name: str, namespace: str) -> None:
@@ -85,7 +82,7 @@ class Distribution(BaseDistribution):
self._dist = dist
# This is populated lazily, to avoid loading metadata for all possible
# distributions eagerly.
- self.__extra_mapping: Optional[Mapping[NormalizedName, str]] = None
+ self.__extra_mapping: Mapping[NormalizedName, str] | None = None
@property
def _extra_mapping(self) -> Mapping[NormalizedName, str]:
@@ -155,11 +152,11 @@ class Distribution(BaseDistribution):
return cls(dist)
@property
- def location(self) -> Optional[str]:
+ def location(self) -> str | None:
return self._dist.location
@property
- def installed_location(self) -> Optional[str]:
+ def installed_location(self) -> str | None:
egg_link = egg_link_path_from_location(self.raw_name)
if egg_link:
location = egg_link
@@ -170,7 +167,7 @@ class Distribution(BaseDistribution):
return normalize_path(location)
@property
- def info_location(self) -> Optional[str]:
+ def info_location(self) -> str | None:
return self._dist.egg_info
@property
@@ -259,14 +256,14 @@ class Environment(BaseEnvironment):
return cls(pkg_resources.working_set)
@classmethod
- def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
+ def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
return cls(pkg_resources.WorkingSet(paths))
def _iter_distributions(self) -> Iterator[BaseDistribution]:
for dist in self._ws:
yield Distribution(dist)
- def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
+ def _search_distribution(self, name: str) -> BaseDistribution | None:
"""Find a distribution matching the ``name`` in the environment.
This searches from *all* distributions available in the environment, to
@@ -278,7 +275,7 @@ class Environment(BaseEnvironment):
return dist
return None
- def get_distribution(self, name: str) -> Optional[BaseDistribution]:
+ def get_distribution(self, name: str) -> BaseDistribution | None:
# Search the distribution by looking through the working set.
dist = self._search_distribution(name)
if dist:
diff --git a/contrib/python/pip/pip/_internal/models/direct_url.py b/contrib/python/pip/pip/_internal/models/direct_url.py
index 8f990dd0ca1..aefc670cd51 100644
--- a/contrib/python/pip/pip/_internal/models/direct_url.py
+++ b/contrib/python/pip/pip/_internal/models/direct_url.py
@@ -1,10 +1,13 @@
"""PEP 610"""
+from __future__ import annotations
+
import json
import re
import urllib.parse
+from collections.abc import Iterable
from dataclasses import dataclass
-from typing import Any, ClassVar, Dict, Iterable, Optional, Type, TypeVar, Union
+from typing import Any, ClassVar, TypeVar, Union
__all__ = [
"DirectUrl",
@@ -25,8 +28,8 @@ class DirectUrlValidationError(Exception):
def _get(
- d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
-) -> Optional[T]:
+ d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None
+) -> T | None:
"""Get value from dictionary and verify expected type."""
if key not in d:
return default
@@ -39,7 +42,7 @@ def _get(
def _get_required(
- d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
+ d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None
) -> T:
value = _get(d, expected_type, key, default)
if value is None:
@@ -47,7 +50,7 @@ def _get_required(
return value
-def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
+def _exactly_one_of(infos: Iterable[InfoType | None]) -> InfoType:
infos = [info for info in infos if info is not None]
if not infos:
raise DirectUrlValidationError(
@@ -61,7 +64,7 @@ def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
return infos[0]
-def _filter_none(**kwargs: Any) -> Dict[str, Any]:
+def _filter_none(**kwargs: Any) -> dict[str, Any]:
"""Make dict excluding None values."""
return {k: v for k, v in kwargs.items() if v is not None}
@@ -72,10 +75,10 @@ class VcsInfo:
vcs: str
commit_id: str
- requested_revision: Optional[str] = None
+ requested_revision: str | None = None
@classmethod
- def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
+ def _from_dict(cls, d: dict[str, Any] | None) -> VcsInfo | None:
if d is None:
return None
return cls(
@@ -84,7 +87,7 @@ class VcsInfo:
requested_revision=_get(d, str, "requested_revision"),
)
- def _to_dict(self) -> Dict[str, Any]:
+ def _to_dict(self) -> dict[str, Any]:
return _filter_none(
vcs=self.vcs,
requested_revision=self.requested_revision,
@@ -97,19 +100,19 @@ class ArchiveInfo:
def __init__(
self,
- hash: Optional[str] = None,
- hashes: Optional[Dict[str, str]] = None,
+ hash: str | None = None,
+ hashes: dict[str, str] | None = None,
) -> None:
# set hashes before hash, since the hash setter will further populate hashes
self.hashes = hashes
self.hash = hash
@property
- def hash(self) -> Optional[str]:
+ def hash(self) -> str | None:
return self._hash
@hash.setter
- def hash(self, value: Optional[str]) -> None:
+ def hash(self, value: str | None) -> None:
if value is not None:
# Auto-populate the hashes key to upgrade to the new format automatically.
# We don't back-populate the legacy hash key from hashes.
@@ -127,12 +130,12 @@ class ArchiveInfo:
self._hash = value
@classmethod
- def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
+ def _from_dict(cls, d: dict[str, Any] | None) -> ArchiveInfo | None:
if d is None:
return None
return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
- def _to_dict(self) -> Dict[str, Any]:
+ def _to_dict(self) -> dict[str, Any]:
return _filter_none(hash=self.hash, hashes=self.hashes)
@@ -143,12 +146,12 @@ class DirInfo:
editable: bool = False
@classmethod
- def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
+ def _from_dict(cls, d: dict[str, Any] | None) -> DirInfo | None:
if d is None:
return None
return cls(editable=_get_required(d, bool, "editable", default=False))
- def _to_dict(self) -> Dict[str, Any]:
+ def _to_dict(self) -> dict[str, Any]:
return _filter_none(editable=self.editable or None)
@@ -159,7 +162,7 @@ InfoType = Union[ArchiveInfo, DirInfo, VcsInfo]
class DirectUrl:
url: str
info: InfoType
- subdirectory: Optional[str] = None
+ subdirectory: str | None = None
def _remove_auth_from_netloc(self, netloc: str) -> str:
if "@" not in netloc:
@@ -192,7 +195,7 @@ class DirectUrl:
self.from_dict(self.to_dict())
@classmethod
- def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
+ def from_dict(cls, d: dict[str, Any]) -> DirectUrl:
return DirectUrl(
url=_get_required(d, str, "url"),
subdirectory=_get(d, str, "subdirectory"),
@@ -205,7 +208,7 @@ class DirectUrl:
),
)
- def to_dict(self) -> Dict[str, Any]:
+ def to_dict(self) -> dict[str, Any]:
res = _filter_none(
url=self.redacted_url,
subdirectory=self.subdirectory,
@@ -214,7 +217,7 @@ class DirectUrl:
return res
@classmethod
- def from_json(cls, s: str) -> "DirectUrl":
+ def from_json(cls, s: str) -> DirectUrl:
return cls.from_dict(json.loads(s))
def to_json(self) -> str:
diff --git a/contrib/python/pip/pip/_internal/models/format_control.py b/contrib/python/pip/pip/_internal/models/format_control.py
index ccd11272c03..9f07e3f3499 100644
--- a/contrib/python/pip/pip/_internal/models/format_control.py
+++ b/contrib/python/pip/pip/_internal/models/format_control.py
@@ -1,4 +1,4 @@
-from typing import FrozenSet, Optional, Set
+from __future__ import annotations
from pip._vendor.packaging.utils import canonicalize_name
@@ -12,8 +12,8 @@ class FormatControl:
def __init__(
self,
- no_binary: Optional[Set[str]] = None,
- only_binary: Optional[Set[str]] = None,
+ no_binary: set[str] | None = None,
+ only_binary: set[str] | None = None,
) -> None:
if no_binary is None:
no_binary = set()
@@ -36,7 +36,7 @@ class FormatControl:
return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})"
@staticmethod
- def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
+ def handle_mutual_excludes(value: str, target: set[str], other: set[str]) -> None:
if value.startswith("-"):
raise CommandError(
"--no-binary / --only-binary option requires 1 argument."
@@ -58,7 +58,7 @@ class FormatControl:
other.discard(name)
target.add(name)
- def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
+ def get_allowed_formats(self, canonical_name: str) -> frozenset[str]:
result = {"binary", "source"}
if canonical_name in self.only_binary:
result.discard("source")
diff --git a/contrib/python/pip/pip/_internal/models/installation_report.py b/contrib/python/pip/pip/_internal/models/installation_report.py
index b9c6330df32..3e8e9683bed 100644
--- a/contrib/python/pip/pip/_internal/models/installation_report.py
+++ b/contrib/python/pip/pip/_internal/models/installation_report.py
@@ -1,4 +1,5 @@
-from typing import Any, Dict, Sequence
+from collections.abc import Sequence
+from typing import Any
from pip._vendor.packaging.markers import default_environment
@@ -11,7 +12,7 @@ class InstallationReport:
self._install_requirements = install_requirements
@classmethod
- def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
+ def _install_req_to_dict(cls, ireq: InstallRequirement) -> dict[str, Any]:
assert ireq.download_info, f"No download_info for {ireq}"
res = {
# PEP 610 json for the download URL. download_info.archive_info.hashes may
@@ -39,7 +40,7 @@ class InstallationReport:
res["requested_extras"] = sorted(ireq.extras)
return res
- def to_dict(self) -> Dict[str, Any]:
+ def to_dict(self) -> dict[str, Any]:
return {
"version": "1",
"pip_version": __version__,
diff --git a/contrib/python/pip/pip/_internal/models/link.py b/contrib/python/pip/pip/_internal/models/link.py
index f0560f6ec26..2e2c0f836ac 100644
--- a/contrib/python/pip/pip/_internal/models/link.py
+++ b/contrib/python/pip/pip/_internal/models/link.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import functools
import itertools
import logging
@@ -5,17 +7,12 @@ import os
import posixpath
import re
import urllib.parse
+from collections.abc import Mapping
from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
Any,
- Dict,
- List,
- Mapping,
NamedTuple,
- Optional,
- Tuple,
- Union,
)
from pip._internal.utils.deprecation import deprecated
@@ -69,8 +66,8 @@ class LinkHash:
assert self.name in _SUPPORTED_HASHES
@classmethod
- @functools.lru_cache(maxsize=None)
- def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
+ @functools.cache
+ def find_hash_url_fragment(cls, url: str) -> LinkHash | None:
"""Search a string for a checksum algorithm name and encoded output value."""
match = cls._hash_url_fragment_re.search(url)
if match is None:
@@ -78,14 +75,14 @@ class LinkHash:
name, value = match.groups()
return cls(name=name, value=value)
- def as_dict(self) -> Dict[str, str]:
+ def as_dict(self) -> dict[str, str]:
return {self.name: self.value}
def as_hashes(self) -> Hashes:
"""Return a Hashes instance which checks only for the current hash."""
return Hashes({self.name: [self.value]})
- def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
+ def is_hash_allowed(self, hashes: Hashes | None) -> bool:
"""
Return True if the current hash is allowed by `hashes`.
"""
@@ -98,14 +95,14 @@ class LinkHash:
class MetadataFile:
"""Information about a core metadata file associated with a distribution."""
- hashes: Optional[Dict[str, str]]
+ hashes: dict[str, str] | None
def __post_init__(self) -> None:
if self.hashes is not None:
assert all(name in _SUPPORTED_HASHES for name in self.hashes)
-def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
+def supported_hashes(hashes: dict[str, str] | None) -> dict[str, str] | None:
# Remove any unsupported hash types from the mapping. If this leaves no
# supported hashes, return None
if hashes is None:
@@ -134,7 +131,11 @@ def _clean_file_url_path(part: str) -> str:
# should not be quoted. On Linux where drive letters do not
# exist, the colon should be quoted. We rely on urllib.request
# to do the right thing here.
- return urllib.request.pathname2url(urllib.request.url2pathname(part))
+ ret = urllib.request.pathname2url(urllib.request.url2pathname(part))
+ if ret.startswith("///"):
+ # Remove any URL authority section, leaving only the URL path.
+ ret = ret.removeprefix("//")
+ return ret
# percent-encoded: /
@@ -175,7 +176,11 @@ def _ensure_quoted_url(url: str) -> str:
# If the netloc is empty, then the URL refers to a local filesystem path.
is_local_path = not result.netloc
path = _clean_url_path(result.path, is_local_path=is_local_path)
- return urllib.parse.urlunsplit(result._replace(path=path))
+ # Temporarily replace scheme with file to ensure the URL generated by
+ # urlunsplit() contains an empty netloc (file://) as per RFC 1738.
+ ret = urllib.parse.urlunsplit(result._replace(scheme="file", path=path))
+ ret = result.scheme + ret[4:] # Restore original scheme.
+ return ret
def _absolute_link_url(base_url: str, url: str) -> str:
@@ -209,12 +214,12 @@ class Link:
def __init__(
self,
url: str,
- comes_from: Optional[Union[str, "IndexContent"]] = None,
- requires_python: Optional[str] = None,
- yanked_reason: Optional[str] = None,
- metadata_file_data: Optional[MetadataFile] = None,
+ comes_from: str | IndexContent | None = None,
+ requires_python: str | None = None,
+ yanked_reason: str | None = None,
+ metadata_file_data: MetadataFile | None = None,
cache_link_parsing: bool = True,
- hashes: Optional[Mapping[str, str]] = None,
+ hashes: Mapping[str, str] | None = None,
) -> None:
"""
:param url: url of the resource pointed to (href of the link)
@@ -274,9 +279,9 @@ class Link:
@classmethod
def from_json(
cls,
- file_data: Dict[str, Any],
+ file_data: dict[str, Any],
page_url: str,
- ) -> Optional["Link"]:
+ ) -> Link | None:
"""
Convert an pypi json document from a simple repository page into a Link.
"""
@@ -325,10 +330,10 @@ class Link:
@classmethod
def from_element(
cls,
- anchor_attribs: Dict[str, Optional[str]],
+ anchor_attribs: dict[str, str | None],
page_url: str,
base_url: str,
- ) -> Optional["Link"]:
+ ) -> Link | None:
"""
Convert an anchor element's attributes in a simple repository page to a Link.
"""
@@ -441,7 +446,7 @@ class Link:
def path(self) -> str:
return self._path
- def splitext(self) -> Tuple[str, str]:
+ def splitext(self) -> tuple[str, str]:
return splitext(posixpath.basename(self.path.rstrip("/")))
@property
@@ -460,7 +465,7 @@ class Link:
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
)
- def _egg_fragment(self) -> Optional[str]:
+ def _egg_fragment(self) -> str | None:
match = self._egg_fragment_re.search(self._url)
if not match:
return None
@@ -472,7 +477,7 @@ class Link:
deprecated(
reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
replacement="to use the req @ url syntax, and remove the egg fragment",
- gone_in="25.2",
+ gone_in="25.3",
issue=13157,
)
@@ -481,13 +486,13 @@ class Link:
_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
@property
- def subdirectory_fragment(self) -> Optional[str]:
+ def subdirectory_fragment(self) -> str | None:
match = self._subdirectory_fragment_re.search(self._url)
if not match:
return None
return match.group(1)
- def metadata_link(self) -> Optional["Link"]:
+ def metadata_link(self) -> Link | None:
"""Return a link to the associated core metadata file (if any)."""
if self.metadata_file_data is None:
return None
@@ -500,11 +505,11 @@ class Link:
return Hashes({k: [v] for k, v in self._hashes.items()})
@property
- def hash(self) -> Optional[str]:
+ def hash(self) -> str | None:
return next(iter(self._hashes.values()), None)
@property
- def hash_name(self) -> Optional[str]:
+ def hash_name(self) -> str | None:
return next(iter(self._hashes), None)
@property
@@ -536,7 +541,7 @@ class Link:
def has_hash(self) -> bool:
return bool(self._hashes)
- def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
+ def is_hash_allowed(self, hashes: Hashes | None) -> bool:
"""
Return True if the link has a hash and it is allowed by `hashes`.
"""
@@ -572,9 +577,9 @@ class _CleanResult(NamedTuple):
"""
parsed: urllib.parse.SplitResult
- query: Dict[str, List[str]]
+ query: dict[str, list[str]]
subdirectory: str
- hashes: Dict[str, str]
+ hashes: dict[str, str]
def _clean_link(link: Link) -> _CleanResult:
@@ -603,6 +608,6 @@ def _clean_link(link: Link) -> _CleanResult:
)
[email protected]_cache(maxsize=None)
def links_equivalent(link1: Link, link2: Link) -> bool:
return _clean_link(link1) == _clean_link(link2)
diff --git a/contrib/python/pip/pip/_internal/models/pylock.py b/contrib/python/pip/pip/_internal/models/pylock.py
index d9decb2964f..1b6b8c1270b 100644
--- a/contrib/python/pip/pip/_internal/models/pylock.py
+++ b/contrib/python/pip/pip/_internal/models/pylock.py
@@ -1,17 +1,22 @@
+from __future__ import annotations
+
import dataclasses
import re
+from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
-from typing import Any, Dict, Iterable, List, Optional, Tuple
+from typing import TYPE_CHECKING, Any
from pip._vendor import tomli_w
-from pip._vendor.typing_extensions import Self
from pip._internal.models.direct_url import ArchiveInfo, DirInfo, VcsInfo
from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.urls import url_to_path
+if TYPE_CHECKING:
+ from typing_extensions import Self
+
PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
@@ -19,70 +24,70 @@ def is_valid_pylock_file_name(path: Path) -> bool:
return path.name == "pylock.toml" or bool(re.match(PYLOCK_FILE_NAME_RE, path.name))
-def _toml_dict_factory(data: List[Tuple[str, Any]]) -> Dict[str, Any]:
+def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
return {key.replace("_", "-"): value for key, value in data if value is not None}
@dataclass
class PackageVcs:
type: str
- url: Optional[str]
+ url: str | None
# (not supported) path: Optional[str]
- requested_revision: Optional[str]
+ requested_revision: str | None
commit_id: str
- subdirectory: Optional[str]
+ subdirectory: str | None
@dataclass
class PackageDirectory:
path: str
- editable: Optional[bool]
- subdirectory: Optional[str]
+ editable: bool | None
+ subdirectory: str | None
@dataclass
class PackageArchive:
- url: Optional[str]
+ url: str | None
# (not supported) path: Optional[str]
# (not supported) size: Optional[int]
# (not supported) upload_time: Optional[datetime]
- hashes: Dict[str, str]
- subdirectory: Optional[str]
+ hashes: dict[str, str]
+ subdirectory: str | None
@dataclass
class PackageSdist:
name: str
# (not supported) upload_time: Optional[datetime]
- url: Optional[str]
+ url: str | None
# (not supported) path: Optional[str]
# (not supported) size: Optional[int]
- hashes: Dict[str, str]
+ hashes: dict[str, str]
@dataclass
class PackageWheel:
name: str
# (not supported) upload_time: Optional[datetime]
- url: Optional[str]
+ url: str | None
# (not supported) path: Optional[str]
# (not supported) size: Optional[int]
- hashes: Dict[str, str]
+ hashes: dict[str, str]
@dataclass
class Package:
name: str
- version: Optional[str] = None
+ version: str | None = None
# (not supported) marker: Optional[str]
# (not supported) requires_python: Optional[str]
# (not supported) dependencies
- vcs: Optional[PackageVcs] = None
- directory: Optional[PackageDirectory] = None
- archive: Optional[PackageArchive] = None
+ vcs: PackageVcs | None = None
+ directory: PackageDirectory | None = None
+ archive: PackageArchive | None = None
# (not supported) index: Optional[str]
- sdist: Optional[PackageSdist] = None
- wheels: Optional[List[PackageWheel]] = None
+ sdist: PackageSdist | None = None
+ wheels: list[PackageWheel] | None = None
# (not supported) attestation_identities: Optional[List[Dict[str, Any]]]
# (not supported) tool: Optional[Dict[str, Any]]
@@ -162,7 +167,7 @@ class Pylock:
# (not supported) extras: List[str] = []
# (not supported) dependency_groups: List[str] = []
created_by: str = "pip"
- packages: List[Package] = dataclasses.field(default_factory=list)
+ packages: list[Package] = dataclasses.field(default_factory=list)
# (not supported) tool: Optional[Dict[str, Any]]
def as_toml(self) -> str:
diff --git a/contrib/python/pip/pip/_internal/models/search_scope.py b/contrib/python/pip/pip/_internal/models/search_scope.py
index ee7bc86229a..136163ca096 100644
--- a/contrib/python/pip/pip/_internal/models/search_scope.py
+++ b/contrib/python/pip/pip/_internal/models/search_scope.py
@@ -4,7 +4,6 @@ import os
import posixpath
import urllib.parse
from dataclasses import dataclass
-from typing import List
from pip._vendor.packaging.utils import canonicalize_name
@@ -23,15 +22,15 @@ class SearchScope:
__slots__ = ["find_links", "index_urls", "no_index"]
- find_links: List[str]
- index_urls: List[str]
+ find_links: list[str]
+ index_urls: list[str]
no_index: bool
@classmethod
def create(
cls,
- find_links: List[str],
- index_urls: List[str],
+ find_links: list[str],
+ index_urls: list[str],
no_index: bool,
) -> "SearchScope":
"""
@@ -42,7 +41,7 @@ class SearchScope:
# it and if it exists, use the normalized version.
# This is deliberately conservative - it might be fine just to
# blindly normalize anything starting with a ~...
- built_find_links: List[str] = []
+ built_find_links: list[str] = []
for link in find_links:
if link.startswith("~"):
new_link = normalize_path(link)
@@ -104,7 +103,7 @@ class SearchScope:
)
return "\n".join(lines)
- def get_index_urls_locations(self, project_name: str) -> List[str]:
+ def get_index_urls_locations(self, project_name: str) -> list[str]:
"""Returns the locations found via self.index_urls
Checks the url_name on the main (first in the list) index and
diff --git a/contrib/python/pip/pip/_internal/models/selection_prefs.py b/contrib/python/pip/pip/_internal/models/selection_prefs.py
index e9b50aa5175..8d5b42dfa31 100644
--- a/contrib/python/pip/pip/_internal/models/selection_prefs.py
+++ b/contrib/python/pip/pip/_internal/models/selection_prefs.py
@@ -1,4 +1,4 @@
-from typing import Optional
+from __future__ import annotations
from pip._internal.models.format_control import FormatControl
@@ -27,9 +27,9 @@ class SelectionPreferences:
self,
allow_yanked: bool,
allow_all_prereleases: bool = False,
- format_control: Optional[FormatControl] = None,
+ format_control: FormatControl | None = None,
prefer_binary: bool = False,
- ignore_requires_python: Optional[bool] = None,
+ ignore_requires_python: bool | None = None,
) -> None:
"""Create a SelectionPreferences object.
diff --git a/contrib/python/pip/pip/_internal/models/target_python.py b/contrib/python/pip/pip/_internal/models/target_python.py
index 88925a9fd01..8c38392d8bb 100644
--- a/contrib/python/pip/pip/_internal/models/target_python.py
+++ b/contrib/python/pip/pip/_internal/models/target_python.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import sys
-from typing import List, Optional, Set, Tuple
from pip._vendor.packaging.tags import Tag
@@ -26,10 +27,10 @@ class TargetPython:
def __init__(
self,
- platforms: Optional[List[str]] = None,
- py_version_info: Optional[Tuple[int, ...]] = None,
- abis: Optional[List[str]] = None,
- implementation: Optional[str] = None,
+ platforms: list[str] | None = None,
+ py_version_info: tuple[int, ...] | None = None,
+ abis: list[str] | None = None,
+ implementation: str | None = None,
) -> None:
"""
:param platforms: A list of strings or None. If None, searches for
@@ -62,8 +63,8 @@ class TargetPython:
self.py_version_info = py_version_info
# This is used to cache the return value of get_(un)sorted_tags.
- self._valid_tags: Optional[List[Tag]] = None
- self._valid_tags_set: Optional[Set[Tag]] = None
+ self._valid_tags: list[Tag] | None = None
+ self._valid_tags_set: set[Tag] | None = None
def format_given(self) -> str:
"""
@@ -85,7 +86,7 @@ class TargetPython:
f"{key}={value!r}" for key, value in key_values if value is not None
)
- def get_sorted_tags(self) -> List[Tag]:
+ def get_sorted_tags(self) -> list[Tag]:
"""
Return the supported PEP 425 tags to check wheel candidates against.
@@ -110,7 +111,7 @@ class TargetPython:
return self._valid_tags
- def get_unsorted_tags(self) -> Set[Tag]:
+ def get_unsorted_tags(self) -> set[Tag]:
"""Exactly the same as get_sorted_tags, but returns a set.
This is important for performance.
diff --git a/contrib/python/pip/pip/_internal/models/wheel.py b/contrib/python/pip/pip/_internal/models/wheel.py
index d905d652e3c..60e97cb762b 100644
--- a/contrib/python/pip/pip/_internal/models/wheel.py
+++ b/contrib/python/pip/pip/_internal/models/wheel.py
@@ -2,8 +2,10 @@
name that have meaning.
"""
+from __future__ import annotations
+
import re
-from typing import Dict, Iterable, List, Optional
+from collections.abc import Iterable
from pip._vendor.packaging.tags import Tag
from pip._vendor.packaging.utils import BuildTag, parse_wheel_filename
@@ -31,7 +33,7 @@ class Wheel:
# To make mypy happy specify type hints that can come from either
# parse_wheel_filename or the legacy_wheel_file_re match.
self.name: str
- self._build_tag: Optional[BuildTag] = None
+ self._build_tag: BuildTag | None = None
try:
wheel_info = parse_wheel_filename(filename)
@@ -88,11 +90,11 @@ class Wheel:
return self._build_tag
- def get_formatted_file_tags(self) -> List[str]:
+ def get_formatted_file_tags(self) -> list[str]:
"""Return the wheel's tags as a sorted list of strings."""
return sorted(str(tag) for tag in self.file_tags)
- def support_index_min(self, tags: List[Tag]) -> int:
+ def support_index_min(self, tags: list[Tag]) -> int:
"""Return the lowest index that one of the wheel's file_tag combinations
achieves in the given list of supported tags.
@@ -111,7 +113,7 @@ class Wheel:
raise ValueError()
def find_most_preferred_tag(
- self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
+ self, tags: list[Tag], tag_to_priority: dict[Tag, int]
) -> int:
"""Return the priority of the most preferred tag that one of the wheel's file
tag combinations achieves in the given list of supported tags using the given
diff --git a/contrib/python/pip/pip/_internal/network/auth.py b/contrib/python/pip/pip/_internal/network/auth.py
index 1a2606ed080..a42f7024c4c 100644
--- a/contrib/python/pip/pip/_internal/network/auth.py
+++ b/contrib/python/pip/pip/_internal/network/auth.py
@@ -4,6 +4,8 @@ Contains interface (MultiDomainBasicAuth) and associated glue code for
providing credentials in the context of network requests.
"""
+from __future__ import annotations
+
import logging
import os
import shutil
@@ -12,10 +14,10 @@ import sysconfig
import typing
import urllib.parse
from abc import ABC, abstractmethod
-from functools import lru_cache
+from functools import cache
from os.path import commonprefix
from pathlib import Path
-from typing import Any, Dict, List, NamedTuple, Optional, Tuple
+from typing import Any, NamedTuple
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
from pip._vendor.requests.models import Request, Response
@@ -48,9 +50,7 @@ class KeyRingBaseProvider(ABC):
has_keyring: bool
@abstractmethod
- def get_auth_info(
- self, url: str, username: Optional[str]
- ) -> Optional[AuthInfo]: ...
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None: ...
@abstractmethod
def save_auth_info(self, url: str, username: str, password: str) -> None: ...
@@ -61,7 +61,7 @@ class KeyRingNullProvider(KeyRingBaseProvider):
has_keyring = False
- def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
return None
def save_auth_info(self, url: str, username: str, password: str) -> None:
@@ -78,7 +78,7 @@ class KeyRingPythonProvider(KeyRingBaseProvider):
self.keyring = keyring
- def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
# Support keyring's get_credential interface which supports getting
# credentials without a username. This is only available for
# keyring>=15.2.0.
@@ -114,7 +114,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
def __init__(self, cmd: str) -> None:
self.keyring = cmd
- def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
# This is the default implementation of keyring.get_credential
# https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
if username is not None:
@@ -126,7 +126,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
def save_auth_info(self, url: str, username: str, password: str) -> None:
return self._set_password(url, username, password)
- def _get_password(self, service_name: str, username: str) -> Optional[str]:
+ def _get_password(self, service_name: str, username: str) -> str | None:
"""Mirror the implementation of keyring.get_password using cli"""
if self.keyring is None:
return None
@@ -159,7 +159,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
return None
-@lru_cache(maxsize=None)
+@cache
def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
logger.verbose("Keyring provider requested: %s", provider)
@@ -225,19 +225,19 @@ class MultiDomainBasicAuth(AuthBase):
def __init__(
self,
prompting: bool = True,
- index_urls: Optional[List[str]] = None,
+ index_urls: list[str] | None = None,
keyring_provider: str = "auto",
) -> None:
self.prompting = prompting
self.index_urls = index_urls
- self.keyring_provider = keyring_provider # type: ignore[assignment]
- self.passwords: Dict[str, AuthInfo] = {}
+ self.keyring_provider = keyring_provider
+ self.passwords: dict[str, AuthInfo] = {}
# When the user is prompted to enter credentials and keyring is
# available, we will offer to save them. If the user accepts,
# this value is set to the credentials they entered. After the
# request authenticates, the caller should call
# ``save_credentials`` to save these.
- self._credentials_to_save: Optional[Credentials] = None
+ self._credentials_to_save: Credentials | None = None
@property
def keyring_provider(self) -> KeyRingBaseProvider:
@@ -260,9 +260,9 @@ class MultiDomainBasicAuth(AuthBase):
def _get_keyring_auth(
self,
- url: Optional[str],
- username: Optional[str],
- ) -> Optional[AuthInfo]:
+ url: str | None,
+ username: str | None,
+ ) -> AuthInfo | None:
"""Return the tuple auth for a given url from keyring."""
# Do nothing if no url was provided
if not url:
@@ -284,7 +284,7 @@ class MultiDomainBasicAuth(AuthBase):
get_keyring_provider.cache_clear()
return None
- def _get_index_url(self, url: str) -> Optional[str]:
+ def _get_index_url(self, url: str) -> str | None:
"""Return the original index URL matching the requested URL.
Cached or dynamically generated credentials may work against
@@ -391,7 +391,7 @@ class MultiDomainBasicAuth(AuthBase):
def _get_url_and_credentials(
self, original_url: str
- ) -> Tuple[str, Optional[str], Optional[str]]:
+ ) -> tuple[str, str | None, str | None]:
"""Return the credentials to use for the provided URL.
If allowed, netrc and keyring may be used to obtain the
@@ -454,9 +454,7 @@ class MultiDomainBasicAuth(AuthBase):
return req
# Factored out to allow for easy patching in tests
- def _prompt_for_password(
- self, netloc: str
- ) -> Tuple[Optional[str], Optional[str], bool]:
+ def _prompt_for_password(self, netloc: str) -> tuple[str | None, str | None, bool]:
username = ask_input(f"User for {netloc}: ") if self.prompting else None
if not username:
return None, None, False
diff --git a/contrib/python/pip/pip/_internal/network/cache.py b/contrib/python/pip/pip/_internal/network/cache.py
index 2fe00f40263..0c5961c45b4 100644
--- a/contrib/python/pip/pip/_internal/network/cache.py
+++ b/contrib/python/pip/pip/_internal/network/cache.py
@@ -1,9 +1,13 @@
"""HTTP cache implementation."""
+from __future__ import annotations
+
import os
+import shutil
+from collections.abc import Generator
from contextlib import contextmanager
from datetime import datetime
-from typing import BinaryIO, Generator, Optional, Union
+from typing import Any, BinaryIO, Callable
from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
@@ -59,7 +63,7 @@ class SafeFileCache(SeparateBodyBaseCache):
parts = list(hashed[:5]) + [hashed]
return os.path.join(self.directory, *parts)
- def get(self, key: str) -> Optional[bytes]:
+ def get(self, key: str) -> bytes | None:
# The cache entry is only valid if both metadata and body exist.
metadata_path = self._get_cache_path(key)
body_path = metadata_path + ".body"
@@ -69,12 +73,13 @@ class SafeFileCache(SeparateBodyBaseCache):
with open(metadata_path, "rb") as f:
return f.read()
- def _write(self, path: str, data: bytes) -> None:
+ def _write_to_file(self, path: str, writer_func: Callable[[BinaryIO], Any]) -> None:
+ """Common file writing logic with proper permissions and atomic replacement."""
with suppressed_cache_errors():
ensure_dir(os.path.dirname(path))
with adjacent_tmp_file(path) as f:
- f.write(data)
+ writer_func(f)
# Inherit the read/write permissions of the cache directory
# to enable multi-user cache use-cases.
mode = (
@@ -90,8 +95,14 @@ class SafeFileCache(SeparateBodyBaseCache):
replace(f.name, path)
+ def _write(self, path: str, data: bytes) -> None:
+ self._write_to_file(path, lambda f: f.write(data))
+
+ def _write_from_io(self, path: str, source_file: BinaryIO) -> None:
+ self._write_to_file(path, lambda f: shutil.copyfileobj(source_file, f))
+
def set(
- self, key: str, value: bytes, expires: Union[int, datetime, None] = None
+ self, key: str, value: bytes, expires: int | datetime | None = None
) -> None:
path = self._get_cache_path(key)
self._write(path, value)
@@ -103,7 +114,7 @@ class SafeFileCache(SeparateBodyBaseCache):
with suppressed_cache_errors():
os.remove(path + ".body")
- def get_body(self, key: str) -> Optional[BinaryIO]:
+ def get_body(self, key: str) -> BinaryIO | None:
# The cache entry is only valid if both metadata and body exist.
metadata_path = self._get_cache_path(key)
body_path = metadata_path + ".body"
@@ -115,3 +126,8 @@ class SafeFileCache(SeparateBodyBaseCache):
def set_body(self, key: str, body: bytes) -> None:
path = self._get_cache_path(key) + ".body"
self._write(path, body)
+
+ def set_body_from_io(self, key: str, body_file: BinaryIO) -> None:
+ """Set the body of the cache entry from a file object."""
+ path = self._get_cache_path(key) + ".body"
+ self._write_from_io(path, body_file)
diff --git a/contrib/python/pip/pip/_internal/network/download.py b/contrib/python/pip/pip/_internal/network/download.py
index 15ef58b9c93..9881cc285fa 100644
--- a/contrib/python/pip/pip/_internal/network/download.py
+++ b/contrib/python/pip/pip/_internal/network/download.py
@@ -1,35 +1,42 @@
"""Download files with progress indicators."""
+from __future__ import annotations
+
import email.message
import logging
import mimetypes
import os
+from collections.abc import Iterable, Mapping
+from dataclasses import dataclass
from http import HTTPStatus
-from typing import BinaryIO, Iterable, Optional, Tuple
+from typing import BinaryIO
+from pip._vendor.requests import PreparedRequest
from pip._vendor.requests.models import Response
+from pip._vendor.urllib3 import HTTPResponse as URLlib3Response
+from pip._vendor.urllib3._collections import HTTPHeaderDict
from pip._vendor.urllib3.exceptions import ReadTimeoutError
-from pip._internal.cli.progress_bars import get_download_progress_renderer
+from pip._internal.cli.progress_bars import BarType, get_download_progress_renderer
from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError
from pip._internal.models.index import PyPI
from pip._internal.models.link import Link
-from pip._internal.network.cache import is_from_cache
-from pip._internal.network.session import PipSession
+from pip._internal.network.cache import SafeFileCache, is_from_cache
+from pip._internal.network.session import CacheControlAdapter, PipSession
from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext
logger = logging.getLogger(__name__)
-def _get_http_response_size(resp: Response) -> Optional[int]:
+def _get_http_response_size(resp: Response) -> int | None:
try:
return int(resp.headers["content-length"])
except (ValueError, KeyError, TypeError):
return None
-def _get_http_response_etag_or_last_modified(resp: Response) -> Optional[str]:
+def _get_http_response_etag_or_last_modified(resp: Response) -> str | None:
"""
Return either the ETag or Last-Modified header (or None if neither exists).
The return value can be used in an If-Range header.
@@ -37,12 +44,12 @@ def _get_http_response_etag_or_last_modified(resp: Response) -> Optional[str]:
return resp.headers.get("etag", resp.headers.get("last-modified"))
-def _prepare_download(
+def _log_download(
resp: Response,
link: Link,
- progress_bar: str,
- total_length: Optional[int],
- range_start: Optional[int] = 0,
+ progress_bar: BarType,
+ total_length: int | None,
+ range_start: int | None = 0,
) -> Iterable[bytes]:
if link.netloc == PyPI.file_storage_domain:
url = link.show_url
@@ -119,7 +126,7 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
content_disposition = resp.headers.get("content-disposition")
if content_disposition:
filename = parse_content_disposition(content_disposition, filename)
- ext: Optional[str] = splitext(filename)[1]
+ ext: str | None = splitext(filename)[1]
if not ext:
ext = mimetypes.guess_extension(resp.headers.get("content-type", ""))
if ext:
@@ -131,35 +138,35 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
return filename
-def _http_get_download(
- session: PipSession,
- link: Link,
- range_start: Optional[int] = 0,
- if_range: Optional[str] = None,
-) -> Response:
- target_url = link.url.split("#", 1)[0]
- headers = HEADERS.copy()
- # request a partial download
- if range_start:
- headers["Range"] = f"bytes={range_start}-"
- # make sure the file hasn't changed
- if if_range:
- headers["If-Range"] = if_range
- try:
- resp = session.get(target_url, headers=headers, stream=True)
- raise_for_status(resp)
- except NetworkConnectionError as e:
- assert e.response is not None
- logger.critical("HTTP error %s while getting %s", e.response.status_code, link)
- raise
- return resp
+@dataclass
+class _FileDownload:
+ """Stores the state of a single link download."""
+
+ link: Link
+ output_file: BinaryIO
+ size: int | None
+ bytes_received: int = 0
+ reattempts: int = 0
+
+ def is_incomplete(self) -> bool:
+ return bool(self.size is not None and self.bytes_received < self.size)
+
+ def write_chunk(self, data: bytes) -> None:
+ self.bytes_received += len(data)
+ self.output_file.write(data)
+
+ def reset_file(self) -> None:
+ """Delete any saved data and reset progress to zero."""
+ self.output_file.seek(0)
+ self.output_file.truncate()
+ self.bytes_received = 0
class Downloader:
def __init__(
self,
session: PipSession,
- progress_bar: str,
+ progress_bar: BarType,
resume_retries: int,
) -> None:
assert (
@@ -169,146 +176,167 @@ class Downloader:
self._progress_bar = progress_bar
self._resume_retries = resume_retries
- def __call__(self, link: Link, location: str) -> Tuple[str, str]:
- """Download the file given by link into location."""
- resp = _http_get_download(self._session, link)
- # NOTE: The original download size needs to be passed down everywhere
- # so if the download is resumed (with a HTTP Range request) the progress
- # bar will report the right size.
- total_length = _get_http_response_size(resp)
- content_type = resp.headers.get("Content-Type", "")
+ def batch(
+ self, links: Iterable[Link], location: str
+ ) -> Iterable[tuple[Link, tuple[str, str]]]:
+ """Convenience method to download multiple links."""
+ for link in links:
+ filepath, content_type = self(link, location)
+ yield link, (filepath, content_type)
- filename = _get_http_response_filename(resp, link)
- filepath = os.path.join(location, filename)
+ def __call__(self, link: Link, location: str) -> tuple[str, str]:
+ """Download a link and save it under location."""
+ resp = self._http_get(link)
+ download_size = _get_http_response_size(resp)
+ filepath = os.path.join(location, _get_http_response_filename(resp, link))
with open(filepath, "wb") as content_file:
- bytes_received = self._process_response(
- resp, link, content_file, 0, total_length
- )
- # If possible, check for an incomplete download and attempt resuming.
- if total_length and bytes_received < total_length:
- self._attempt_resume(
- resp, link, content_file, total_length, bytes_received
- )
+ download = _FileDownload(link, content_file, download_size)
+ self._process_response(download, resp)
+ if download.is_incomplete():
+ self._attempt_resumes_or_redownloads(download, resp)
+ content_type = resp.headers.get("Content-Type", "")
return filepath, content_type
- def _process_response(
- self,
- resp: Response,
- link: Link,
- content_file: BinaryIO,
- bytes_received: int,
- total_length: Optional[int],
- ) -> int:
- """Process the response and write the chunks to the file."""
- chunks = _prepare_download(
- resp, link, self._progress_bar, total_length, range_start=bytes_received
+ def _process_response(self, download: _FileDownload, resp: Response) -> None:
+ """Download and save chunks from a response."""
+ chunks = _log_download(
+ resp,
+ download.link,
+ self._progress_bar,
+ download.size,
+ range_start=download.bytes_received,
)
- return self._write_chunks_to_file(
- chunks, content_file, allow_partial=bool(total_length)
- )
-
- def _write_chunks_to_file(
- self, chunks: Iterable[bytes], content_file: BinaryIO, *, allow_partial: bool
- ) -> int:
- """Write the chunks to the file and return the number of bytes received."""
- bytes_received = 0
try:
for chunk in chunks:
- bytes_received += len(chunk)
- content_file.write(chunk)
+ download.write_chunk(chunk)
except ReadTimeoutError as e:
- # If partial downloads are OK (the download will be retried), don't bail.
- if not allow_partial:
+ # If the download size is not known, then give up downloading the file.
+ if download.size is None:
raise e
- # Ensuring bytes_received is returned to attempt resume
logger.warning("Connection timed out while downloading.")
- return bytes_received
-
- def _attempt_resume(
- self,
- resp: Response,
- link: Link,
- content_file: BinaryIO,
- total_length: Optional[int],
- bytes_received: int,
+ def _attempt_resumes_or_redownloads(
+ self, download: _FileDownload, first_resp: Response
) -> None:
- """Attempt to resume the download if connection was dropped."""
- etag_or_last_modified = _get_http_response_etag_or_last_modified(resp)
-
- attempts_left = self._resume_retries
- while total_length and attempts_left and bytes_received < total_length:
- attempts_left -= 1
+ """Attempt to resume/restart the download if connection was dropped."""
+ while download.reattempts < self._resume_retries and download.is_incomplete():
+ assert download.size is not None
+ download.reattempts += 1
logger.warning(
"Attempting to resume incomplete download (%s/%s, attempt %d)",
- format_size(bytes_received),
- format_size(total_length),
- (self._resume_retries - attempts_left),
+ format_size(download.bytes_received),
+ format_size(download.size),
+ download.reattempts,
)
try:
- # Try to resume the download using a HTTP range request.
- resume_resp = _http_get_download(
- self._session,
- link,
- range_start=bytes_received,
- if_range=etag_or_last_modified,
- )
-
+ resume_resp = self._http_get_resume(download, should_match=first_resp)
# Fallback: if the server responded with 200 (i.e., the file has
# since been modified or range requests are unsupported) or any
# other unexpected status, restart the download from the beginning.
must_restart = resume_resp.status_code != HTTPStatus.PARTIAL_CONTENT
if must_restart:
- bytes_received, total_length, etag_or_last_modified = (
- self._reset_download_state(resume_resp, content_file)
- )
+ download.reset_file()
+ download.size = _get_http_response_size(resume_resp)
+ first_resp = resume_resp
- bytes_received += self._process_response(
- resume_resp, link, content_file, bytes_received, total_length
- )
+ self._process_response(download, resume_resp)
except (ConnectionError, ReadTimeoutError, OSError):
continue
# No more resume attempts. Raise an error if the download is still incomplete.
- if total_length and bytes_received < total_length:
- os.remove(content_file.name)
- raise IncompleteDownloadError(
- link, bytes_received, total_length, retries=self._resume_retries
+ if download.is_incomplete():
+ os.remove(download.output_file.name)
+ raise IncompleteDownloadError(download)
+
+ # If we successfully completed the download via resume, manually cache it
+ # as a complete response to enable future caching
+ if download.reattempts > 0:
+ self._cache_resumed_download(download, first_resp)
+
+ def _cache_resumed_download(
+ self, download: _FileDownload, original_response: Response
+ ) -> None:
+ """
+ Manually cache a file that was successfully downloaded via resume retries.
+
+ cachecontrol doesn't cache 206 (Partial Content) responses, since they
+ are not complete files. This method manually adds the final file to the
+ cache as though it was downloaded in a single request, so that future
+ requests can use the cache.
+ """
+ url = download.link.url_without_fragment
+ adapter = self._session.get_adapter(url)
+
+ # Check if the adapter is the CacheControlAdapter (i.e. caching is enabled)
+ if not isinstance(adapter, CacheControlAdapter):
+ logger.debug(
+ "Skipping resume download caching: no cache controller for %s", url
)
+ return
- def _reset_download_state(
- self,
- resp: Response,
- content_file: BinaryIO,
- ) -> Tuple[int, Optional[int], Optional[str]]:
- """Reset the download state to restart downloading from the beginning."""
- content_file.seek(0)
- content_file.truncate()
- bytes_received = 0
- total_length = _get_http_response_size(resp)
- etag_or_last_modified = _get_http_response_etag_or_last_modified(resp)
+ # Check SafeFileCache is being used
+ assert isinstance(
+ adapter.cache, SafeFileCache
+ ), "separate body cache not in use!"
- return bytes_received, total_length, etag_or_last_modified
+ synthetic_request = PreparedRequest()
+ synthetic_request.prepare(method="GET", url=url, headers={})
+ synthetic_response_headers = HTTPHeaderDict()
+ for key, value in original_response.headers.items():
+ if key.lower() not in ["content-range", "content-length"]:
+ synthetic_response_headers[key] = value
+ synthetic_response_headers["content-length"] = str(download.size)
-class BatchDownloader:
- def __init__(
- self,
- session: PipSession,
- progress_bar: str,
- resume_retries: int,
- ) -> None:
- self._downloader = Downloader(session, progress_bar, resume_retries)
+ synthetic_response = URLlib3Response(
+ body="",
+ headers=synthetic_response_headers,
+ status=200,
+ preload_content=False,
+ )
- def __call__(
- self, links: Iterable[Link], location: str
- ) -> Iterable[Tuple[Link, Tuple[str, str]]]:
- """Download the files given by links into location."""
- for link in links:
- filepath, content_type = self._downloader(link, location)
- yield link, (filepath, content_type)
+ # Save metadata and then stream the file contents to cache.
+ cache_url = adapter.controller.cache_url(url)
+ metadata_blob = adapter.controller.serializer.dumps(
+ synthetic_request, synthetic_response, b""
+ )
+ adapter.cache.set(cache_url, metadata_blob)
+ download.output_file.flush()
+ with open(download.output_file.name, "rb") as f:
+ adapter.cache.set_body_from_io(cache_url, f)
+
+ logger.debug(
+ "Cached resumed download as complete response for future use: %s", url
+ )
+
+ def _http_get_resume(
+ self, download: _FileDownload, should_match: Response
+ ) -> Response:
+ """Issue a HTTP range request to resume the download."""
+ # To better understand the download resumption logic, see the mdn web docs:
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests
+ headers = HEADERS.copy()
+ headers["Range"] = f"bytes={download.bytes_received}-"
+ # If possible, use a conditional range request to avoid corrupted
+ # downloads caused by the remote file changing in-between.
+ if identifier := _get_http_response_etag_or_last_modified(should_match):
+ headers["If-Range"] = identifier
+ return self._http_get(download.link, headers)
+
+ def _http_get(self, link: Link, headers: Mapping[str, str] = HEADERS) -> Response:
+ target_url = link.url_without_fragment
+ try:
+ resp = self._session.get(target_url, headers=headers, stream=True)
+ raise_for_status(resp)
+ except NetworkConnectionError as e:
+ assert e.response is not None
+ logger.critical(
+ "HTTP error %s while getting %s", e.response.status_code, link
+ )
+ raise
+ return resp
diff --git a/contrib/python/pip/pip/_internal/network/lazy_wheel.py b/contrib/python/pip/pip/_internal/network/lazy_wheel.py
index 03f883c1fc4..ac3ebe63c9b 100644
--- a/contrib/python/pip/pip/_internal/network/lazy_wheel.py
+++ b/contrib/python/pip/pip/_internal/network/lazy_wheel.py
@@ -1,11 +1,14 @@
"""Lazy ZIP over HTTP"""
+from __future__ import annotations
+
__all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
from bisect import bisect_left, bisect_right
+from collections.abc import Generator
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
-from typing import Any, Dict, Generator, List, Optional, Tuple
+from typing import Any
from zipfile import BadZipFile, ZipFile
from pip._vendor.packaging.utils import canonicalize_name
@@ -56,8 +59,8 @@ class LazyZipOverHTTP:
self._length = int(head.headers["Content-Length"])
self._file = NamedTemporaryFile()
self.truncate(self._length)
- self._left: List[int] = []
- self._right: List[int] = []
+ self._left: list[int] = []
+ self._right: list[int] = []
if "bytes" not in head.headers.get("Accept-Ranges", "none"):
raise HTTPRangeRequestUnsupported("range request is not supported")
self._check_zip()
@@ -117,7 +120,7 @@ class LazyZipOverHTTP:
"""Return the current position."""
return self._file.tell()
- def truncate(self, size: Optional[int] = None) -> int:
+ def truncate(self, size: int | None = None) -> int:
"""Resize the stream to the given size in bytes.
If size is unspecified resize to the current position.
@@ -131,7 +134,7 @@ class LazyZipOverHTTP:
"""Return False."""
return False
- def __enter__(self) -> "LazyZipOverHTTP":
+ def __enter__(self) -> LazyZipOverHTTP:
self._file.__enter__()
return self
@@ -166,7 +169,7 @@ class LazyZipOverHTTP:
break
def _stream_response(
- self, start: int, end: int, base_headers: Dict[str, str] = HEADERS
+ self, start: int, end: int, base_headers: dict[str, str] = HEADERS
) -> Response:
"""Return HTTP response to a range request from start to end."""
headers = base_headers.copy()
@@ -177,7 +180,7 @@ class LazyZipOverHTTP:
def _merge(
self, start: int, end: int, left: int, right: int
- ) -> Generator[Tuple[int, int], None, None]:
+ ) -> Generator[tuple[int, int], None, None]:
"""Return a generator of intervals to be fetched.
Args:
diff --git a/contrib/python/pip/pip/_internal/network/session.py b/contrib/python/pip/pip/_internal/network/session.py
index 5e10f8f5615..a1f9444e37b 100644
--- a/contrib/python/pip/pip/_internal/network/session.py
+++ b/contrib/python/pip/pip/_internal/network/session.py
@@ -2,6 +2,8 @@
network request configuration and behavior.
"""
+from __future__ import annotations
+
import email.utils
import functools
import io
@@ -16,16 +18,11 @@ import subprocess
import sys
import urllib.parse
import warnings
+from collections.abc import Generator, Mapping, Sequence
from typing import (
TYPE_CHECKING,
Any,
- Dict,
- Generator,
- List,
- Mapping,
Optional,
- Sequence,
- Tuple,
Union,
)
@@ -54,18 +51,19 @@ if TYPE_CHECKING:
from ssl import SSLContext
from pip._vendor.urllib3.poolmanager import PoolManager
+ from pip._vendor.urllib3.proxymanager import ProxyManager
logger = logging.getLogger(__name__)
-SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
+SecureOrigin = tuple[str, str, Optional[Union[int, str]]]
# Ignore warning raised when using --trusted-host.
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
-SECURE_ORIGINS: List[SecureOrigin] = [
+SECURE_ORIGINS: list[SecureOrigin] = [
# protocol, hostname, port
# Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
("https", "*", "*"),
@@ -112,7 +110,7 @@ def user_agent() -> str:
"""
Return a string representing the user agent.
"""
- data: Dict[str, Any] = {
+ data: dict[str, Any] = {
"installer": {"name": "pip", "version": __version__},
"python": platform.python_version(),
"implementation": {
@@ -140,7 +138,7 @@ def user_agent() -> str:
from pip._vendor import distro
linux_distribution = distro.name(), distro.version(), distro.codename()
- distro_infos: Dict[str, Any] = dict(
+ distro_infos: dict[str, Any] = dict(
filter(
lambda x: x[1],
zip(["name", "version", "id"], linux_distribution),
@@ -214,10 +212,10 @@ class LocalFSAdapter(BaseAdapter):
self,
request: PreparedRequest,
stream: bool = False,
- timeout: Optional[Union[float, Tuple[float, float]]] = None,
- verify: Union[bool, str] = True,
- cert: Optional[Union[str, Tuple[str, str]]] = None,
- proxies: Optional[Mapping[str, str]] = None,
+ timeout: float | tuple[float, float] | None = None,
+ verify: bool | str = True,
+ cert: str | tuple[str, str] | None = None,
+ proxies: Mapping[str, str] | None = None,
) -> Response:
pathname = url_to_path(request.url)
@@ -264,7 +262,7 @@ class _SSLContextAdapterMixin:
def __init__(
self,
*,
- ssl_context: Optional["SSLContext"] = None,
+ ssl_context: SSLContext | None = None,
**kwargs: Any,
) -> None:
self._ssl_context = ssl_context
@@ -276,7 +274,7 @@ class _SSLContextAdapterMixin:
maxsize: int,
block: bool = DEFAULT_POOLBLOCK,
**pool_kwargs: Any,
- ) -> "PoolManager":
+ ) -> PoolManager:
if self._ssl_context is not None:
pool_kwargs.setdefault("ssl_context", self._ssl_context)
return super().init_poolmanager( # type: ignore[misc]
@@ -286,6 +284,13 @@ class _SSLContextAdapterMixin:
**pool_kwargs,
)
+ def proxy_manager_for(self, proxy: str, **proxy_kwargs: Any) -> ProxyManager:
+ # Proxy manager replaces the pool manager, so inject our SSL
+ # context here too. https://github.com/pypa/pip/issues/13288
+ if self._ssl_context is not None:
+ proxy_kwargs.setdefault("ssl_context", self._ssl_context)
+ return super().proxy_manager_for(proxy, **proxy_kwargs) # type: ignore[misc]
+
class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
pass
@@ -300,8 +305,8 @@ class InsecureHTTPAdapter(HTTPAdapter):
self,
conn: ConnectionPool,
url: str,
- verify: Union[bool, str],
- cert: Optional[Union[str, Tuple[str, str]]],
+ verify: bool | str,
+ cert: str | tuple[str, str] | None,
) -> None:
super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
@@ -311,23 +316,23 @@ class InsecureCacheControlAdapter(CacheControlAdapter):
self,
conn: ConnectionPool,
url: str,
- verify: Union[bool, str],
- cert: Optional[Union[str, Tuple[str, str]]],
+ verify: bool | str,
+ cert: str | tuple[str, str] | None,
) -> None:
super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
class PipSession(requests.Session):
- timeout: Optional[int] = None
+ timeout: int | None = None
def __init__(
self,
*args: Any,
retries: int = 0,
- cache: Optional[str] = None,
+ cache: str | None = None,
trusted_hosts: Sequence[str] = (),
- index_urls: Optional[List[str]] = None,
- ssl_context: Optional["SSLContext"] = None,
+ index_urls: list[str] | None = None,
+ ssl_context: SSLContext | None = None,
**kwargs: Any,
) -> None:
"""
@@ -338,7 +343,7 @@ class PipSession(requests.Session):
# Namespace the attribute with "pip_" just in case to prevent
# possible conflicts with the base class.
- self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
+ self.pip_trusted_origins: list[tuple[str, int | None]] = []
self.pip_proxy = None
# Attach our User Agent to the request
@@ -401,7 +406,7 @@ class PipSession(requests.Session):
for host in trusted_hosts:
self.add_trusted_host(host, suppress_logging=True)
- def update_index_urls(self, new_index_urls: List[str]) -> None:
+ def update_index_urls(self, new_index_urls: list[str]) -> None:
"""
:param new_index_urls: New index urls to update the authentication
handler with.
@@ -409,7 +414,7 @@ class PipSession(requests.Session):
self.auth.index_urls = new_index_urls
def add_trusted_host(
- self, host: str, source: Optional[str] = None, suppress_logging: bool = False
+ self, host: str, source: str | None = None, suppress_logging: bool = False
) -> None:
"""
:param host: It is okay to provide a host that has previously been
diff --git a/contrib/python/pip/pip/_internal/network/utils.py b/contrib/python/pip/pip/_internal/network/utils.py
index bba4c265e89..74d3111cff0 100644
--- a/contrib/python/pip/pip/_internal/network/utils.py
+++ b/contrib/python/pip/pip/_internal/network/utils.py
@@ -1,4 +1,4 @@
-from typing import Dict, Generator
+from collections.abc import Generator
from pip._vendor.requests.models import Response
@@ -23,7 +23,7 @@ from pip._internal.exceptions import NetworkConnectionError
# you're not asking for a compressed file and will then decompress it
# before sending because if that's the case I don't think it'll ever be
# possible to make this work.
-HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"}
+HEADERS: dict[str, str] = {"Accept-Encoding": "identity"}
DOWNLOAD_CHUNK_SIZE = 256 * 1024
diff --git a/contrib/python/pip/pip/_internal/network/xmlrpc.py b/contrib/python/pip/pip/_internal/network/xmlrpc.py
index ba5caf337e2..f4bddb48a1d 100644
--- a/contrib/python/pip/pip/_internal/network/xmlrpc.py
+++ b/contrib/python/pip/pip/_internal/network/xmlrpc.py
@@ -3,7 +3,7 @@
import logging
import urllib.parse
import xmlrpc.client
-from typing import TYPE_CHECKING, Tuple
+from typing import TYPE_CHECKING
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.network.session import PipSession
@@ -36,7 +36,7 @@ class PipXmlrpcTransport(xmlrpc.client.Transport):
handler: str,
request_body: "SizedBuffer",
verbose: bool = False,
- ) -> Tuple["_Marshallable", ...]:
+ ) -> tuple["_Marshallable", ...]:
assert isinstance(host, str)
parts = (self._scheme, host, handler, None, None, None)
url = urllib.parse.urlunparse(parts)
diff --git a/contrib/python/pip/pip/_internal/operations/build/build_tracker.py b/contrib/python/pip/pip/_internal/operations/build/build_tracker.py
index 0ed8dd23596..cbb4e4f0928 100644
--- a/contrib/python/pip/pip/_internal/operations/build/build_tracker.py
+++ b/contrib/python/pip/pip/_internal/operations/build/build_tracker.py
@@ -1,9 +1,11 @@
+from __future__ import annotations
+
import contextlib
import hashlib
import logging
import os
+from collections.abc import Generator
from types import TracebackType
-from typing import Dict, Generator, Optional, Type, Union
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.temp_dir import TempDirectory
@@ -17,7 +19,7 @@ def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
# Save values from the target and change them.
non_existent_marker = object()
- saved_values: Dict[str, Union[object, str]] = {}
+ saved_values: dict[str, object | str] = {}
for name, new_value in changes.items():
try:
saved_values[name] = target[name]
@@ -38,7 +40,7 @@ def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
@contextlib.contextmanager
-def get_build_tracker() -> Generator["BuildTracker", None, None]:
+def get_build_tracker() -> Generator[BuildTracker, None, None]:
root = os.environ.get("PIP_BUILD_TRACKER")
with contextlib.ExitStack() as ctx:
if root is None:
@@ -65,18 +67,18 @@ class BuildTracker:
def __init__(self, root: str) -> None:
self._root = root
- self._entries: Dict[TrackerId, InstallRequirement] = {}
+ self._entries: dict[TrackerId, InstallRequirement] = {}
logger.debug("Created build tracker: %s", self._root)
- def __enter__(self) -> "BuildTracker":
+ def __enter__(self) -> BuildTracker:
logger.debug("Entered build tracker: %s", self._root)
return self
def __exit__(
self,
- exc_type: Optional[Type[BaseException]],
- exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType],
+ exc_type: type[BaseException] | None,
+ exc_val: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
self.cleanup()
diff --git a/contrib/python/pip/pip/_internal/operations/build/wheel.py b/contrib/python/pip/pip/_internal/operations/build/wheel.py
index 064811ad11b..5e404c6102c 100644
--- a/contrib/python/pip/pip/_internal/operations/build/wheel.py
+++ b/contrib/python/pip/pip/_internal/operations/build/wheel.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import logging
import os
-from typing import Optional
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
@@ -14,7 +15,7 @@ def build_wheel_pep517(
backend: BuildBackendHookCaller,
metadata_directory: str,
tempd: str,
-) -> Optional[str]:
+) -> str | None:
"""Build one InstallRequirement using the PEP 517 build process.
Returns path to wheel if successfully built. Otherwise, returns None.
diff --git a/contrib/python/pip/pip/_internal/operations/build/wheel_editable.py b/contrib/python/pip/pip/_internal/operations/build/wheel_editable.py
index 719d69dd801..521bd556c4a 100644
--- a/contrib/python/pip/pip/_internal/operations/build/wheel_editable.py
+++ b/contrib/python/pip/pip/_internal/operations/build/wheel_editable.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import logging
import os
-from typing import Optional
from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
@@ -14,7 +15,7 @@ def build_wheel_editable(
backend: BuildBackendHookCaller,
metadata_directory: str,
tempd: str,
-) -> Optional[str]:
+) -> str | None:
"""Build one InstallRequirement using the PEP 660 build process.
Returns path to wheel if successfully built. Otherwise, returns None.
diff --git a/contrib/python/pip/pip/_internal/operations/build/wheel_legacy.py b/contrib/python/pip/pip/_internal/operations/build/wheel_legacy.py
index 473018173f5..02ef8e57075 100644
--- a/contrib/python/pip/pip/_internal/operations/build/wheel_legacy.py
+++ b/contrib/python/pip/pip/_internal/operations/build/wheel_legacy.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import logging
import os.path
-from typing import List, Optional
from pip._internal.cli.spinners import open_spinner
from pip._internal.utils.deprecation import deprecated
@@ -11,7 +12,7 @@ logger = logging.getLogger(__name__)
def format_command_result(
- command_args: List[str],
+ command_args: list[str],
command_output: str,
) -> str:
"""Format command information for logging."""
@@ -31,12 +32,12 @@ def format_command_result(
def get_legacy_build_wheel_path(
- names: List[str],
+ names: list[str],
temp_dir: str,
name: str,
- command_args: List[str],
+ command_args: list[str],
command_output: str,
-) -> Optional[str]:
+) -> str | None:
"""Return the path to the wheel in the temporary build directory."""
# Sort for determinism.
names = sorted(names)
@@ -61,10 +62,10 @@ def build_wheel_legacy(
name: str,
setup_py_path: str,
source_dir: str,
- global_options: List[str],
- build_options: List[str],
+ global_options: list[str],
+ build_options: list[str],
tempd: str,
-) -> Optional[str]:
+) -> str | None:
"""Build one unpacked package using the "legacy" build process.
Returns path to wheel if successfully built. Otherwise, returns None.
diff --git a/contrib/python/pip/pip/_internal/operations/check.py b/contrib/python/pip/pip/_internal/operations/check.py
index c6d676d6e61..2d71fa5fff5 100644
--- a/contrib/python/pip/pip/_internal/operations/check.py
+++ b/contrib/python/pip/pip/_internal/operations/check.py
@@ -1,20 +1,15 @@
"""Validation of dependencies of packages"""
+from __future__ import annotations
+
import logging
+from collections.abc import Generator, Iterable
from contextlib import suppress
from email.parser import Parser
from functools import reduce
from typing import (
Callable,
- Dict,
- FrozenSet,
- Generator,
- Iterable,
- List,
NamedTuple,
- Optional,
- Set,
- Tuple,
)
from pip._vendor.packaging.requirements import Requirement
@@ -32,21 +27,21 @@ logger = logging.getLogger(__name__)
class PackageDetails(NamedTuple):
version: Version
- dependencies: List[Requirement]
+ dependencies: list[Requirement]
# Shorthands
-PackageSet = Dict[NormalizedName, PackageDetails]
-Missing = Tuple[NormalizedName, Requirement]
-Conflicting = Tuple[NormalizedName, Version, Requirement]
+PackageSet = dict[NormalizedName, PackageDetails]
+Missing = tuple[NormalizedName, Requirement]
+Conflicting = tuple[NormalizedName, Version, Requirement]
-MissingDict = Dict[NormalizedName, List[Missing]]
-ConflictingDict = Dict[NormalizedName, List[Conflicting]]
-CheckResult = Tuple[MissingDict, ConflictingDict]
-ConflictDetails = Tuple[PackageSet, CheckResult]
+MissingDict = dict[NormalizedName, list[Missing]]
+ConflictingDict = dict[NormalizedName, list[Conflicting]]
+CheckResult = tuple[MissingDict, ConflictingDict]
+ConflictDetails = tuple[PackageSet, CheckResult]
-def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
+def create_package_set_from_installed() -> tuple[PackageSet, bool]:
"""Converts a list of distributions into a PackageSet."""
package_set = {}
problems = False
@@ -64,7 +59,7 @@ def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
def check_package_set(
- package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
+ package_set: PackageSet, should_ignore: Callable[[str], bool] | None = None
) -> CheckResult:
"""Check if a package set is consistent
@@ -77,8 +72,8 @@ def check_package_set(
for package_name, package_detail in package_set.items():
# Info about dependencies of package_name
- missing_deps: Set[Missing] = set()
- conflicting_deps: Set[Conflicting] = set()
+ missing_deps: set[Missing] = set()
+ conflicting_deps: set[Conflicting] = set()
if should_ignore and should_ignore(package_name):
continue
@@ -108,7 +103,7 @@ def check_package_set(
return missing, conflicting
-def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
+def check_install_conflicts(to_install: list[InstallRequirement]) -> ConflictDetails:
"""For checking if the dependency graph would be consistent after \
installing given requirements
"""
@@ -135,7 +130,7 @@ def check_unsupported(
for p in packages:
with suppress(FileNotFoundError):
wheel_file = p.read_text("WHEEL")
- wheel_tags: FrozenSet[Tag] = reduce(
+ wheel_tags: frozenset[Tag] = reduce(
frozenset.union,
map(parse_tag, Parser().parsestr(wheel_file).get_all("Tag", [])),
frozenset(),
@@ -145,8 +140,8 @@ def check_unsupported(
def _simulate_installation_of(
- to_install: List[InstallRequirement], package_set: PackageSet
-) -> Set[NormalizedName]:
+ to_install: list[InstallRequirement], package_set: PackageSet
+) -> set[NormalizedName]:
"""Computes the version of packages after installing to_install."""
# Keep track of packages that were installed
installed = set()
@@ -164,8 +159,8 @@ def _simulate_installation_of(
def _create_whitelist(
- would_be_installed: Set[NormalizedName], package_set: PackageSet
-) -> Set[NormalizedName]:
+ would_be_installed: set[NormalizedName], package_set: PackageSet
+) -> set[NormalizedName]:
packages_affected = set(would_be_installed)
for package_name in package_set:
diff --git a/contrib/python/pip/pip/_internal/operations/freeze.py b/contrib/python/pip/pip/_internal/operations/freeze.py
index ae5dd37f9db..486a833212f 100644
--- a/contrib/python/pip/pip/_internal/operations/freeze.py
+++ b/contrib/python/pip/pip/_internal/operations/freeze.py
@@ -1,8 +1,11 @@
+from __future__ import annotations
+
import collections
import logging
import os
+from collections.abc import Container, Generator, Iterable
from dataclasses import dataclass, field
-from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
+from typing import NamedTuple
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import InvalidVersion
@@ -21,19 +24,19 @@ logger = logging.getLogger(__name__)
class _EditableInfo(NamedTuple):
requirement: str
- comments: List[str]
+ comments: list[str]
def freeze(
- requirement: Optional[List[str]] = None,
+ requirement: list[str] | None = None,
local_only: bool = False,
user_only: bool = False,
- paths: Optional[List[str]] = None,
+ paths: list[str] | None = None,
isolated: bool = False,
exclude_editable: bool = False,
skip: Container[str] = (),
) -> Generator[str, None, None]:
- installations: Dict[str, FrozenRequirement] = {}
+ installations: dict[str, FrozenRequirement] = {}
dists = get_environment(paths).iter_installed_distributions(
local_only=local_only,
@@ -51,10 +54,10 @@ def freeze(
# should only be emitted once, even if the same option is in multiple
# requirements files, so we need to keep track of what has been emitted
# so that we don't emit it again if it's seen again
- emitted_options: Set[str] = set()
+ emitted_options: set[str] = set()
# keep track of which files a requirement is in so that we can
# give an accurate warning if a requirement appears multiple times.
- req_files: Dict[str, List[str]] = collections.defaultdict(list)
+ req_files: dict[str, list[str]] = collections.defaultdict(list)
for req_file_path in requirement:
with open(req_file_path) as req_file:
for line in req_file:
@@ -83,7 +86,7 @@ def freeze(
yield line
continue
- if line.startswith("-e") or line.startswith("--editable"):
+ if line.startswith(("-e", "--editable")):
if line.startswith("-e"):
line = line[2:].strip()
else:
@@ -233,7 +236,7 @@ class FrozenRequirement:
return canonicalize_name(self.name)
@classmethod
- def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
+ def from_dist(cls, dist: BaseDistribution) -> FrozenRequirement:
editable = dist.editable
if editable:
req, comments = _get_editable_info(dist)
diff --git a/contrib/python/pip/pip/_internal/operations/install/editable_legacy.py b/contrib/python/pip/pip/_internal/operations/install/editable_legacy.py
index 644bcec111f..0603d3d8819 100644
--- a/contrib/python/pip/pip/_internal/operations/install/editable_legacy.py
+++ b/contrib/python/pip/pip/_internal/operations/install/editable_legacy.py
@@ -1,7 +1,9 @@
"""Legacy editable installation process, i.e. `setup.py develop`."""
+from __future__ import annotations
+
import logging
-from typing import Optional, Sequence
+from collections.abc import Sequence
from pip._internal.build_env import BuildEnvironment
from pip._internal.utils.logging import indent_log
@@ -14,8 +16,8 @@ logger = logging.getLogger(__name__)
def install_editable(
*,
global_options: Sequence[str],
- prefix: Optional[str],
- home: Optional[str],
+ prefix: str | None,
+ home: str | None,
use_user_site: bool,
name: str,
setup_py_path: str,
diff --git a/contrib/python/pip/pip/_internal/operations/install/wheel.py b/contrib/python/pip/pip/_internal/operations/install/wheel.py
index cfc3b26ebbc..2724f150f7b 100644
--- a/contrib/python/pip/pip/_internal/operations/install/wheel.py
+++ b/contrib/python/pip/pip/_internal/operations/install/wheel.py
@@ -1,5 +1,7 @@
"""Support for installing and building the "wheel" binary package format."""
+from __future__ import annotations
+
import collections
import compileall
import contextlib
@@ -10,8 +12,10 @@ import os.path
import re
import shutil
import sys
+import textwrap
import warnings
from base64 import urlsafe_b64encode
+from collections.abc import Generator, Iterable, Iterator, Sequence
from email.message import Message
from itertools import chain, filterfalse, starmap
from typing import (
@@ -19,17 +23,8 @@ from typing import (
Any,
BinaryIO,
Callable,
- Dict,
- Generator,
- Iterable,
- Iterator,
- List,
NewType,
- Optional,
Protocol,
- Sequence,
- Set,
- Tuple,
Union,
cast,
)
@@ -60,7 +55,7 @@ from pip._internal.utils.wheel import parse_wheel
class File(Protocol):
- src_record_path: "RecordPath"
+ src_record_path: RecordPath
dest_path: str
changed: bool
@@ -71,17 +66,17 @@ class File(Protocol):
logger = logging.getLogger(__name__)
RecordPath = NewType("RecordPath", str)
-InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
+InstalledCSVRow = tuple[RecordPath, str, Union[int, str]]
-def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]:
+def rehash(path: str, blocksize: int = 1 << 20) -> tuple[str, str]:
"""Return (encoded_digest, length) for path using hashlib.sha256()"""
h, length = hash_file(path, blocksize)
digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=")
return (digest, str(length))
-def csv_io_kwargs(mode: str) -> Dict[str, Any]:
+def csv_io_kwargs(mode: str) -> dict[str, Any]:
"""Return keyword arguments to properly open a CSV file
in the given mode.
"""
@@ -112,7 +107,7 @@ def wheel_root_is_purelib(metadata: Message) -> bool:
return metadata.get("Root-Is-Purelib", "").lower() == "true"
-def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]:
+def get_entrypoints(dist: BaseDistribution) -> tuple[dict[str, str], dict[str, str]]:
console_scripts = {}
gui_scripts = {}
for entry_point in dist.iter_entry_points():
@@ -123,7 +118,7 @@ def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, s
return console_scripts, gui_scripts
-def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
+def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
"""Determine if any scripts are not on PATH and format a warning.
Returns a warning message if one or more scripts are not on PATH,
otherwise None.
@@ -132,7 +127,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
return None
# Group scripts by the path they were installed in
- grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
+ grouped_by_dir: dict[str, set[str]] = collections.defaultdict(set)
for destfile in scripts:
parent_dir = os.path.dirname(destfile)
script_name = os.path.basename(destfile)
@@ -148,7 +143,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
not_warn_dirs.append(
os.path.normcase(os.path.normpath(os.path.dirname(sys.executable)))
)
- warn_for: Dict[str, Set[str]] = {
+ warn_for: dict[str, set[str]] = {
parent_dir: scripts
for parent_dir, scripts in grouped_by_dir.items()
if os.path.normcase(os.path.normpath(parent_dir)) not in not_warn_dirs
@@ -159,7 +154,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
# Format a message
msg_lines = []
for parent_dir, dir_scripts in warn_for.items():
- sorted_scripts: List[str] = sorted(dir_scripts)
+ sorted_scripts: list[str] = sorted(dir_scripts)
if len(sorted_scripts) == 1:
start_text = f"script {sorted_scripts[0]} is"
else:
@@ -197,7 +192,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
def _normalized_outrows(
outrows: Iterable[InstalledCSVRow],
-) -> List[Tuple[str, str, str]]:
+) -> list[tuple[str, str, str]]:
"""Normalize the given rows of a RECORD file.
Items in each row are converted into str. Rows are then sorted to make
@@ -236,17 +231,17 @@ def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath:
def get_csv_rows_for_installed(
- old_csv_rows: List[List[str]],
- installed: Dict[RecordPath, RecordPath],
- changed: Set[RecordPath],
- generated: List[str],
+ old_csv_rows: list[list[str]],
+ installed: dict[RecordPath, RecordPath],
+ changed: set[RecordPath],
+ generated: list[str],
lib_dir: str,
-) -> List[InstalledCSVRow]:
+) -> list[InstalledCSVRow]:
"""
:param installed: A map from archive RECORD path to installation RECORD
path.
"""
- installed_rows: List[InstalledCSVRow] = []
+ installed_rows: list[InstalledCSVRow] = []
for row in old_csv_rows:
if len(row) > 3:
logger.warning("RECORD line has more than three elements: %s", row)
@@ -267,7 +262,7 @@ def get_csv_rows_for_installed(
]
-def get_console_script_specs(console: Dict[str, str]) -> List[str]:
+def get_console_script_specs(console: dict[str, str]) -> list[str]:
"""
Given the mapping from entrypoint name to callable, return the relevant
console script specs.
@@ -381,7 +376,7 @@ class ZipBackedFile:
class ScriptFile:
- def __init__(self, file: "File") -> None:
+ def __init__(self, file: File) -> None:
self._file = file
self.src_record_path = self._file.src_record_path
self.dest_path = self._file.dest_path
@@ -396,7 +391,7 @@ class MissingCallableSuffix(InstallationError):
def __init__(self, entry_point: str) -> None:
super().__init__(
f"Invalid script entry point: {entry_point} - A callable "
- "suffix is required. Cf https://packaging.python.org/"
+ "suffix is required. See https://packaging.python.org/"
"specifications/entry-points/#use-for-scripts for more "
"information."
)
@@ -409,9 +404,22 @@ def _raise_for_invalid_entrypoint(specification: str) -> None:
class PipScriptMaker(ScriptMaker):
+ # Override distlib's default script template with one that
+ # doesn't import `re` module, allowing scripts to load faster.
+ script_template = textwrap.dedent(
+ """\
+ import sys
+ from %(module)s import %(import_name)s
+ if __name__ == '__main__':
+ if sys.argv[0].endswith('.exe'):
+ sys.argv[0] = sys.argv[0][:-4]
+ sys.exit(%(func)s())
+"""
+ )
+
def make(
- self, specification: str, options: Optional[Dict[str, Any]] = None
- ) -> List[str]:
+ self, specification: str, options: dict[str, Any] | None = None
+ ) -> list[str]:
_raise_for_invalid_entrypoint(specification)
return super().make(specification, options)
@@ -423,7 +431,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
scheme: Scheme,
pycompile: bool = True,
warn_script_location: bool = True,
- direct_url: Optional[DirectUrl] = None,
+ direct_url: DirectUrl | None = None,
requested: bool = False,
) -> None:
"""Install a wheel.
@@ -452,9 +460,9 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
# installed = files copied from the wheel to the destination
# changed = files changed while installing (scripts #! line typically)
# generated = files newly generated during the install (script wrappers)
- installed: Dict[RecordPath, RecordPath] = {}
- changed: Set[RecordPath] = set()
- generated: List[str] = []
+ installed: dict[RecordPath, RecordPath] = {}
+ changed: set[RecordPath] = set()
+ generated: list[str] = []
def record_installed(
srcfile: RecordPath, destfile: str, modified: bool = False
@@ -480,8 +488,8 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
def root_scheme_file_maker(
zip_file: ZipFile, dest: str
- ) -> Callable[[RecordPath], "File"]:
- def make_root_scheme_file(record_path: RecordPath) -> "File":
+ ) -> Callable[[RecordPath], File]:
+ def make_root_scheme_file(record_path: RecordPath) -> File:
normed_path = os.path.normpath(record_path)
dest_path = os.path.join(dest, normed_path)
assert_no_path_traversal(dest, dest_path)
@@ -491,10 +499,10 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
def data_scheme_file_maker(
zip_file: ZipFile, scheme: Scheme
- ) -> Callable[[RecordPath], "File"]:
+ ) -> Callable[[RecordPath], File]:
scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}
- def make_data_scheme_file(record_path: RecordPath) -> "File":
+ def make_data_scheme_file(record_path: RecordPath) -> File:
normed_path = os.path.normpath(record_path)
try:
_, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
@@ -526,7 +534,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
def is_data_scheme_path(path: RecordPath) -> bool:
return path.split("/", 1)[0].endswith(".data")
- paths = cast(List[RecordPath], wheel_zip.namelist())
+ paths = cast(list[RecordPath], wheel_zip.namelist())
file_paths = filterfalse(is_dir_path, paths)
root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths)
@@ -552,7 +560,7 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long
)
console, gui = get_entrypoints(distribution)
- def is_entrypoint_wrapper(file: "File") -> bool:
+ def is_entrypoint_wrapper(file: File) -> bool:
# EP, EP.exe and EP-script.py are scripts generated for
# entry point EP by setuptools
path = file.dest_path
@@ -721,7 +729,7 @@ def install_wheel(
req_description: str,
pycompile: bool = True,
warn_script_location: bool = True,
- direct_url: Optional[DirectUrl] = None,
+ direct_url: DirectUrl | None = None,
requested: bool = False,
) -> None:
with ZipFile(wheel_path, allowZip64=True) as z:
diff --git a/contrib/python/pip/pip/_internal/operations/prepare.py b/contrib/python/pip/pip/_internal/operations/prepare.py
index 531070a03ad..00b1a33a030 100644
--- a/contrib/python/pip/pip/_internal/operations/prepare.py
+++ b/contrib/python/pip/pip/_internal/operations/prepare.py
@@ -2,16 +2,19 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
+from __future__ import annotations
import mimetypes
import os
import shutil
+from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
-from typing import Dict, Iterable, List, Optional
+from typing import TYPE_CHECKING
from pip._vendor.packaging.utils import canonicalize_name
+from pip._internal.build_env import BuildEnvironmentInstaller
from pip._internal.distributions import make_distribution_for_install_requirement
from pip._internal.distributions.installed import InstalledDistribution
from pip._internal.exceptions import (
@@ -28,7 +31,7 @@ from pip._internal.metadata import BaseDistribution, get_metadata_distribution
from pip._internal.models.direct_url import ArchiveInfo
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
-from pip._internal.network.download import BatchDownloader, Downloader
+from pip._internal.network.download import Downloader
from pip._internal.network.lazy_wheel import (
HTTPRangeRequestUnsupported,
dist_from_wheel_url,
@@ -53,13 +56,16 @@ from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.unpacking import unpack_file
from pip._internal.vcs import vcs
+if TYPE_CHECKING:
+ from pip._internal.cli.progress_bars import BarType
+
logger = getLogger(__name__)
def _get_prepared_distribution(
req: InstallRequirement,
build_tracker: BuildTracker,
- finder: PackageFinder,
+ build_env_installer: BuildEnvironmentInstaller,
build_isolation: bool,
check_build_deps: bool,
) -> BaseDistribution:
@@ -69,7 +75,7 @@ def _get_prepared_distribution(
if tracker_id is not None:
with build_tracker.track(req, tracker_id):
abstract_dist.prepare_distribution_metadata(
- finder, build_isolation, check_build_deps
+ build_env_installer, build_isolation, check_build_deps
)
return abstract_dist.get_metadata_distribution()
@@ -83,7 +89,7 @@ def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
@dataclass
class File:
path: str
- content_type: Optional[str] = None
+ content_type: str | None = None
def __post_init__(self) -> None:
if self.content_type is None:
@@ -98,8 +104,8 @@ class File:
def get_http_url(
link: Link,
download: Downloader,
- download_dir: Optional[str] = None,
- hashes: Optional[Hashes] = None,
+ download_dir: str | None = None,
+ hashes: Hashes | None = None,
) -> File:
temp_dir = TempDirectory(kind="unpack", globally_managed=True)
# If a download dir is specified, is the file already downloaded there?
@@ -120,7 +126,7 @@ def get_http_url(
def get_file_url(
- link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
+ link: Link, download_dir: str | None = None, hashes: Hashes | None = None
) -> File:
"""Get file and optionally check its hash."""
# If a download dir is specified, is the file already there and valid?
@@ -148,9 +154,9 @@ def unpack_url(
location: str,
download: Downloader,
verbosity: int,
- download_dir: Optional[str] = None,
- hashes: Optional[Hashes] = None,
-) -> Optional[File]:
+ download_dir: str | None = None,
+ hashes: Hashes | None = None,
+) -> File | None:
"""Unpack link into location, downloading if required.
:param hashes: A Hashes object, one of whose embedded hashes must match,
@@ -189,9 +195,9 @@ def unpack_url(
def _check_download_dir(
link: Link,
download_dir: str,
- hashes: Optional[Hashes],
+ hashes: Hashes | None,
warn_on_hash_mismatch: bool = True,
-) -> Optional[str]:
+) -> str | None:
"""Check download_dir for previously downloaded file with correct hash
If a correct file is found return its path else None
"""
@@ -219,16 +225,18 @@ def _check_download_dir(
class RequirementPreparer:
"""Prepares a Requirement"""
- def __init__(
+ def __init__( # noqa: PLR0913 (too many parameters)
self,
+ *,
build_dir: str,
- download_dir: Optional[str],
+ download_dir: str | None,
src_dir: str,
build_isolation: bool,
+ build_isolation_installer: BuildEnvironmentInstaller,
check_build_deps: bool,
build_tracker: BuildTracker,
session: PipSession,
- progress_bar: str,
+ progress_bar: BarType,
finder: PackageFinder,
require_hashes: bool,
use_user_site: bool,
@@ -244,7 +252,6 @@ class RequirementPreparer:
self.build_tracker = build_tracker
self._session = session
self._download = Downloader(session, progress_bar, resume_retries)
- self._batch_download = BatchDownloader(session, progress_bar, resume_retries)
self.finder = finder
# Where still-packed archives should be written to. If None, they are
@@ -253,6 +260,7 @@ class RequirementPreparer:
# Is build isolation allowed?
self.build_isolation = build_isolation
+ self.build_env_installer = build_isolation_installer
# Should check build dependencies?
self.check_build_deps = check_build_deps
@@ -273,7 +281,7 @@ class RequirementPreparer:
self.legacy_resolver = legacy_resolver
# Memoized downloaded files, as mapping of url: path.
- self._downloaded: Dict[str, str] = {}
+ self._downloaded: dict[str, str] = {}
# Previous "header" printed for a link-based InstallRequirement
self._previous_requirement_header = ("", "")
@@ -291,7 +299,7 @@ class RequirementPreparer:
# would already be included if we used req directly)
if req.req and req.comes_from:
if isinstance(req.comes_from, str):
- comes_from: Optional[str] = req.comes_from
+ comes_from: str | None = req.comes_from
else:
comes_from = req.comes_from.from_path()
if comes_from:
@@ -363,7 +371,7 @@ class RequirementPreparer:
def _fetch_metadata_only(
self,
req: InstallRequirement,
- ) -> Optional[BaseDistribution]:
+ ) -> BaseDistribution | None:
if self.legacy_resolver:
logger.debug(
"Metadata-only fetching is not used in the legacy resolver",
@@ -382,7 +390,7 @@ class RequirementPreparer:
def _fetch_metadata_using_link_data_attr(
self,
req: InstallRequirement,
- ) -> Optional[BaseDistribution]:
+ ) -> BaseDistribution | None:
"""Fetch metadata from the data-dist-info-metadata attribute, if possible."""
# (1) Get the link to the metadata file, if provided by the backend.
metadata_link = req.link.metadata_link()
@@ -423,7 +431,7 @@ class RequirementPreparer:
def _fetch_metadata_using_lazy_wheel(
self,
link: Link,
- ) -> Optional[BaseDistribution]:
+ ) -> BaseDistribution | None:
"""Fetch metadata using lazy wheel, if possible."""
# --use-feature=fast-deps must be provided.
if not self.use_lazy_wheel:
@@ -462,15 +470,12 @@ class RequirementPreparer:
# Map each link to the requirement that owns it. This allows us to set
# `req.local_file_path` on the appropriate requirement after passing
# all the links at once into BatchDownloader.
- links_to_fully_download: Dict[Link, InstallRequirement] = {}
+ links_to_fully_download: dict[Link, InstallRequirement] = {}
for req in partially_downloaded_reqs:
assert req.link
links_to_fully_download[req.link] = req
- batch_download = self._batch_download(
- links_to_fully_download.keys(),
- temp_dir,
- )
+ batch_download = self._download.batch(links_to_fully_download.keys(), temp_dir)
for link, (filepath, _) in batch_download:
logger.debug("Downloading link %s to %s", link, filepath)
req = links_to_fully_download[link]
@@ -547,7 +552,7 @@ class RequirementPreparer:
# Prepare requirements we found were already downloaded for some
# reason. The other downloads will be completed separately.
- partially_downloaded_reqs: List[InstallRequirement] = []
+ partially_downloaded_reqs: list[InstallRequirement] = []
for req in reqs:
if req.needs_more_preparation:
partially_downloaded_reqs.append(req)
@@ -647,7 +652,7 @@ class RequirementPreparer:
dist = _get_prepared_distribution(
req,
self.build_tracker,
- self.finder,
+ self.build_env_installer,
self.build_isolation,
self.check_build_deps,
)
@@ -703,7 +708,7 @@ class RequirementPreparer:
dist = _get_prepared_distribution(
req,
self.build_tracker,
- self.finder,
+ self.build_env_installer,
self.build_isolation,
self.check_build_deps,
)
diff --git a/contrib/python/pip/pip/_internal/pyproject.py b/contrib/python/pip/pip/_internal/pyproject.py
index 0e8452f39dc..5c08b60e5b5 100644
--- a/contrib/python/pip/pip/_internal/pyproject.py
+++ b/contrib/python/pip/pip/_internal/pyproject.py
@@ -1,13 +1,9 @@
+from __future__ import annotations
+
import importlib.util
import os
-import sys
from collections import namedtuple
-from typing import Any, List, Optional
-
-if sys.version_info >= (3, 11):
- import tomllib
-else:
- from pip._vendor import tomli as tomllib
+from typing import Any
from pip._vendor.packaging.requirements import InvalidRequirement
@@ -16,6 +12,7 @@ from pip._internal.exceptions import (
InvalidPyProjectBuildRequires,
MissingPyProjectBuildRequires,
)
+from pip._internal.utils.compat import tomllib
from pip._internal.utils.packaging import get_requirement
@@ -33,8 +30,8 @@ BuildSystemDetails = namedtuple(
def load_pyproject_toml(
- use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
-) -> Optional[BuildSystemDetails]:
+ use_pep517: bool | None, pyproject_toml: str, setup_py: str, req_name: str
+) -> BuildSystemDetails | None:
"""Load the pyproject.toml file.
Parameters:
@@ -166,7 +163,7 @@ def load_pyproject_toml(
backend = build_system.get("build-backend")
backend_path = build_system.get("backend-path", [])
- check: List[str] = []
+ check: list[str] = []
if backend is None:
# If the user didn't specify a backend, we assume they want to use
# the setuptools backend. But we can't be sure they have included
diff --git a/contrib/python/pip/pip/_internal/req/__init__.py b/contrib/python/pip/pip/_internal/req/__init__.py
index bf282dab8bc..e5050ee588b 100644
--- a/contrib/python/pip/pip/_internal/req/__init__.py
+++ b/contrib/python/pip/pip/_internal/req/__init__.py
@@ -1,9 +1,11 @@
+from __future__ import annotations
+
import collections
import logging
+from collections.abc import Generator, Sequence
from dataclasses import dataclass
-from typing import Generator, List, Optional, Sequence, Tuple
-from pip._internal.cli.progress_bars import get_install_progress_renderer
+from pip._internal.cli.progress_bars import BarType, get_install_progress_renderer
from pip._internal.utils.logging import indent_log
from .req_file import parse_requirements
@@ -26,24 +28,24 @@ class InstallationResult:
def _validate_requirements(
- requirements: List[InstallRequirement],
-) -> Generator[Tuple[str, InstallRequirement], None, None]:
+ requirements: list[InstallRequirement],
+) -> Generator[tuple[str, InstallRequirement], None, None]:
for req in requirements:
assert req.name, f"invalid to-be-installed requirement: {req}"
yield req.name, req
def install_given_reqs(
- requirements: List[InstallRequirement],
+ requirements: list[InstallRequirement],
global_options: Sequence[str],
- root: Optional[str],
- home: Optional[str],
- prefix: Optional[str],
+ root: str | None,
+ home: str | None,
+ prefix: str | None,
warn_script_location: bool,
use_user_site: bool,
pycompile: bool,
- progress_bar: str,
-) -> List[InstallationResult]:
+ progress_bar: BarType,
+) -> list[InstallationResult]:
"""
Install everything in the given list.
diff --git a/contrib/python/pip/pip/_internal/req/constructors.py b/contrib/python/pip/pip/_internal/req/constructors.py
index 56a964f3177..056e7e3a7f1 100644
--- a/contrib/python/pip/pip/_internal/req/constructors.py
+++ b/contrib/python/pip/pip/_internal/req/constructors.py
@@ -8,12 +8,14 @@ These are meant to be used elsewhere within pip to create instances of
InstallRequirement.
"""
+from __future__ import annotations
+
import copy
import logging
import os
import re
+from collections.abc import Collection
from dataclasses import dataclass
-from typing import Collection, Dict, List, Optional, Set, Tuple, Union
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
@@ -41,7 +43,7 @@ logger = logging.getLogger(__name__)
operators = Specifier._operators.keys()
-def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
+def _strip_extras(path: str) -> tuple[str, str | None]:
m = re.match(r"^(.+)(\[[^\]]+\])$", path)
extras = None
if m:
@@ -53,19 +55,19 @@ def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
return path_no_extras, extras
-def convert_extras(extras: Optional[str]) -> Set[str]:
+def convert_extras(extras: str | None) -> set[str]:
if not extras:
return set()
return get_requirement("placeholder" + extras.lower()).extras
-def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requirement:
+def _set_requirement_extras(req: Requirement, new_extras: set[str]) -> Requirement:
"""
Returns a new requirement based on the given one, with the supplied extras. If the
given requirement already has extras those are replaced (or dropped if no new extras
are given).
"""
- match: Optional[re.Match[str]] = re.fullmatch(
+ match: re.Match[str] | None = re.fullmatch(
# see https://peps.python.org/pep-0508/#complete-grammar
r"([\w\t .-]+)(\[[^\]]*\])?(.*)",
str(req),
@@ -75,8 +77,8 @@ def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requireme
assert (
match is not None
), f"regex match on requirement {req} failed, this should never happen"
- pre: Optional[str] = match.group(1)
- post: Optional[str] = match.group(3)
+ pre: str | None = match.group(1)
+ post: str | None = match.group(3)
assert (
pre is not None and post is not None
), f"regex group selection for requirement {req} failed, this should never happen"
@@ -84,7 +86,7 @@ def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requireme
return get_requirement(f"{pre}{extras}{post}")
-def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
+def parse_editable(editable_req: str) -> tuple[str | None, str, set[str]]:
"""Parses an editable requirement into:
- a requirement name
- an URL
@@ -194,10 +196,10 @@ def deduce_helpful_msg(req: str) -> str:
@dataclass(frozen=True)
class RequirementParts:
- requirement: Optional[Requirement]
- link: Optional[Link]
- markers: Optional[Marker]
- extras: Set[str]
+ requirement: Requirement | None
+ link: Link | None
+ markers: Marker | None
+ extras: set[str]
def parse_req_from_editable(editable_req: str) -> RequirementParts:
@@ -205,7 +207,7 @@ def parse_req_from_editable(editable_req: str) -> RequirementParts:
if name is not None:
try:
- req: Optional[Requirement] = get_requirement(name)
+ req: Requirement | None = get_requirement(name)
except InvalidRequirement as exc:
raise InstallationError(f"Invalid requirement: {name!r}: {exc}")
else:
@@ -221,16 +223,16 @@ def parse_req_from_editable(editable_req: str) -> RequirementParts:
def install_req_from_editable(
editable_req: str,
- comes_from: Optional[Union[InstallRequirement, str]] = None,
+ comes_from: InstallRequirement | str | None = None,
*,
- use_pep517: Optional[bool] = None,
+ use_pep517: bool | None = None,
isolated: bool = False,
- global_options: Optional[List[str]] = None,
- hash_options: Optional[Dict[str, List[str]]] = None,
+ global_options: list[str] | None = None,
+ hash_options: dict[str, list[str]] | None = None,
constraint: bool = False,
user_supplied: bool = False,
permit_editable_wheels: bool = False,
- config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+ config_settings: dict[str, str | list[str]] | None = None,
) -> InstallRequirement:
parts = parse_req_from_editable(editable_req)
@@ -270,7 +272,7 @@ def _looks_like_path(name: str) -> bool:
return False
-def _get_url_from_path(path: str, name: str) -> Optional[str]:
+def _get_url_from_path(path: str, name: str) -> str | None:
"""
First, it checks whether a provided path is an installable directory. If it
is, returns the path.
@@ -304,7 +306,7 @@ def _get_url_from_path(path: str, name: str) -> Optional[str]:
return path_to_url(path)
-def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
+def parse_req_from_line(name: str, line_source: str | None) -> RequirementParts:
if is_url(name):
marker_sep = "; "
else:
@@ -376,7 +378,7 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
raise InstallationError(msg)
if req_as_string is not None:
- req: Optional[Requirement] = _parse_req_string(req_as_string)
+ req: Requirement | None = _parse_req_string(req_as_string)
else:
req = None
@@ -385,16 +387,16 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
def install_req_from_line(
name: str,
- comes_from: Optional[Union[str, InstallRequirement]] = None,
+ comes_from: str | InstallRequirement | None = None,
*,
- use_pep517: Optional[bool] = None,
+ use_pep517: bool | None = None,
isolated: bool = False,
- global_options: Optional[List[str]] = None,
- hash_options: Optional[Dict[str, List[str]]] = None,
+ global_options: list[str] | None = None,
+ hash_options: dict[str, list[str]] | None = None,
constraint: bool = False,
- line_source: Optional[str] = None,
+ line_source: str | None = None,
user_supplied: bool = False,
- config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+ config_settings: dict[str, str | list[str]] | None = None,
) -> InstallRequirement:
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
@@ -422,9 +424,9 @@ def install_req_from_line(
def install_req_from_req_string(
req_string: str,
- comes_from: Optional[InstallRequirement] = None,
+ comes_from: InstallRequirement | None = None,
isolated: bool = False,
- use_pep517: Optional[bool] = None,
+ use_pep517: bool | None = None,
user_supplied: bool = False,
) -> InstallRequirement:
try:
@@ -461,9 +463,9 @@ def install_req_from_req_string(
def install_req_from_parsed_requirement(
parsed_req: ParsedRequirement,
isolated: bool = False,
- use_pep517: Optional[bool] = None,
+ use_pep517: bool | None = None,
user_supplied: bool = False,
- config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+ config_settings: dict[str, str | list[str]] | None = None,
) -> InstallRequirement:
if parsed_req.is_editable:
req = install_req_from_editable(
diff --git a/contrib/python/pip/pip/_internal/req/req_dependency_group.py b/contrib/python/pip/pip/_internal/req/req_dependency_group.py
index e81dd45522a..396ac1bb635 100644
--- a/contrib/python/pip/pip/_internal/req/req_dependency_group.py
+++ b/contrib/python/pip/pip/_internal/req/req_dependency_group.py
@@ -1,17 +1,13 @@
-import sys
-from typing import Any, Dict, Iterable, Iterator, List, Tuple
-
-if sys.version_info >= (3, 11):
- import tomllib
-else:
- from pip._vendor import tomli as tomllib
+from collections.abc import Iterable, Iterator
+from typing import Any
from pip._vendor.dependency_groups import DependencyGroupResolver
from pip._internal.exceptions import InstallationError
+from pip._internal.utils.compat import tomllib
-def parse_dependency_groups(groups: List[Tuple[str, str]]) -> List[str]:
+def parse_dependency_groups(groups: list[tuple[str, str]]) -> list[str]:
"""
Parse dependency groups data as provided via the CLI, in a `[path:]group` syntax.
@@ -22,7 +18,7 @@ def parse_dependency_groups(groups: List[Tuple[str, str]]) -> List[str]:
def _resolve_all_groups(
- resolvers: Dict[str, DependencyGroupResolver], groups: List[Tuple[str, str]]
+ resolvers: dict[str, DependencyGroupResolver], groups: list[tuple[str, str]]
) -> Iterator[str]:
"""
Run all resolution, converting any error from `DependencyGroupResolver` into
@@ -39,7 +35,7 @@ def _resolve_all_groups(
) from e
-def _build_resolvers(paths: Iterable[str]) -> Dict[str, Any]:
+def _build_resolvers(paths: Iterable[str]) -> dict[str, Any]:
resolvers = {}
for path in paths:
if path in resolvers:
@@ -62,7 +58,7 @@ def _build_resolvers(paths: Iterable[str]) -> Dict[str, Any]:
return resolvers
-def _load_pyproject(path: str) -> Dict[str, Any]:
+def _load_pyproject(path: str) -> dict[str, Any]:
"""
This helper loads a pyproject.toml as TOML.
diff --git a/contrib/python/pip/pip/_internal/req/req_file.py b/contrib/python/pip/pip/_internal/req/req_file.py
index f6ba70fe7f6..0aad0a36602 100644
--- a/contrib/python/pip/pip/_internal/req/req_file.py
+++ b/contrib/python/pip/pip/_internal/req/req_file.py
@@ -2,6 +2,8 @@
Requirements file parsing
"""
+from __future__ import annotations
+
import codecs
import locale
import logging
@@ -11,19 +13,14 @@ import re
import shlex
import sys
import urllib.parse
+from collections.abc import Generator, Iterable
from dataclasses import dataclass
from optparse import Values
from typing import (
TYPE_CHECKING,
Any,
Callable,
- Dict,
- Generator,
- Iterable,
- List,
NoReturn,
- Optional,
- Tuple,
)
from pip._internal.cli import cmdoptions
@@ -36,9 +33,9 @@ if TYPE_CHECKING:
__all__ = ["parse_requirements"]
-ReqFileLines = Iterable[Tuple[int, str]]
+ReqFileLines = Iterable[tuple[int, str]]
-LineParser = Callable[[str], Tuple[str, Values]]
+LineParser = Callable[[str], tuple[str, Values]]
SCHEME_RE = re.compile(r"^(http|https|file):", re.I)
COMMENT_RE = re.compile(r"(^|\s+)#.*$")
@@ -49,7 +46,7 @@ COMMENT_RE = re.compile(r"(^|\s+)#.*$")
# 2013 Edition.
ENV_VAR_RE = re.compile(r"(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})")
-SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
+SUPPORTED_OPTIONS: list[Callable[..., optparse.Option]] = [
cmdoptions.index_url,
cmdoptions.extra_index_url,
cmdoptions.no_index,
@@ -67,13 +64,13 @@ SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
]
# options to be passed to requirements
-SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
+SUPPORTED_OPTIONS_REQ: list[Callable[..., optparse.Option]] = [
cmdoptions.global_options,
cmdoptions.hash,
cmdoptions.config_settings,
]
-SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [
+SUPPORTED_OPTIONS_EDITABLE_REQ: list[Callable[..., optparse.Option]] = [
cmdoptions.config_settings,
]
@@ -86,7 +83,7 @@ SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
# order of BOMS is important: codecs.BOM_UTF16_LE is a prefix of codecs.BOM_UTF32_LE
# so data.startswith(BOM_UTF16_LE) would be true for UTF32_LE data
-BOMS: List[Tuple[bytes, str]] = [
+BOMS: list[tuple[bytes, str]] = [
(codecs.BOM_UTF8, "utf-8"),
(codecs.BOM_UTF32, "utf-32"),
(codecs.BOM_UTF32_BE, "utf-32-be"),
@@ -118,8 +115,8 @@ class ParsedRequirement:
is_editable: bool
comes_from: str
constraint: bool
- options: Optional[Dict[str, Any]]
- line_source: Optional[str]
+ options: dict[str, Any] | None
+ line_source: str | None
@dataclass(frozen=True)
@@ -137,7 +134,7 @@ class ParsedLine:
return bool(self.opts.editables)
@property
- def requirement(self) -> Optional[str]:
+ def requirement(self) -> str | None:
if self.args:
return self.args
elif self.is_editable:
@@ -148,9 +145,9 @@ class ParsedLine:
def parse_requirements(
filename: str,
- session: "PipSession",
- finder: Optional["PackageFinder"] = None,
- options: Optional[optparse.Values] = None,
+ session: PipSession,
+ finder: PackageFinder | None = None,
+ options: optparse.Values | None = None,
constraint: bool = False,
) -> Generator[ParsedRequirement, None, None]:
"""Parse a requirements file and yield ParsedRequirement instances.
@@ -187,7 +184,7 @@ def preprocess(content: str) -> ReqFileLines:
def handle_requirement_line(
line: ParsedLine,
- options: Optional[optparse.Values] = None,
+ options: optparse.Values | None = None,
) -> ParsedRequirement:
# preserve for the nested code path
line_comes_from = "{} {} (line {})".format(
@@ -223,9 +220,9 @@ def handle_option_line(
opts: Values,
filename: str,
lineno: int,
- finder: Optional["PackageFinder"] = None,
- options: Optional[optparse.Values] = None,
- session: Optional["PipSession"] = None,
+ finder: PackageFinder | None = None,
+ options: optparse.Values | None = None,
+ session: PipSession | None = None,
) -> None:
if opts.hashes:
logger.warning(
@@ -291,10 +288,10 @@ def handle_option_line(
def handle_line(
line: ParsedLine,
- options: Optional[optparse.Values] = None,
- finder: Optional["PackageFinder"] = None,
- session: Optional["PipSession"] = None,
-) -> Optional[ParsedRequirement]:
+ options: optparse.Values | None = None,
+ finder: PackageFinder | None = None,
+ session: PipSession | None = None,
+) -> ParsedRequirement | None:
"""Handle a single parsed requirements line; This can result in
creating/yielding requirements, or updating the finder.
@@ -336,7 +333,7 @@ def handle_line(
class RequirementsFileParser:
def __init__(
self,
- session: "PipSession",
+ session: PipSession,
line_parser: LineParser,
) -> None:
self._session = session
@@ -354,7 +351,7 @@ class RequirementsFileParser:
self,
filename: str,
constraint: bool,
- parsed_files_stack: List[Dict[str, Optional[str]]],
+ parsed_files_stack: list[dict[str, str | None]],
) -> Generator[ParsedLine, None, None]:
for line in self._parse_file(filename, constraint):
if line.requirement is None and (
@@ -426,8 +423,8 @@ class RequirementsFileParser:
)
-def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
- def parse_line(line: str) -> Tuple[str, Values]:
+def get_line_parser(finder: PackageFinder | None) -> LineParser:
+ def parse_line(line: str) -> tuple[str, Values]:
# Build new parser for each line since it accumulates appendable
# options.
parser = build_parser()
@@ -450,7 +447,7 @@ def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
return parse_line
-def break_args_options(line: str) -> Tuple[str, str]:
+def break_args_options(line: str) -> tuple[str, str]:
"""Break up the line into an args and options string. We only want to shlex
(and then optparse) the options, not the args. args can contain markers
which are corrupted by shlex.
@@ -459,7 +456,7 @@ def break_args_options(line: str) -> Tuple[str, str]:
args = []
options = tokens[:]
for token in tokens:
- if token.startswith("-") or token.startswith("--"):
+ if token.startswith(("-", "--")):
break
else:
args.append(token)
@@ -485,7 +482,7 @@ def build_parser() -> optparse.OptionParser:
# By default optparse sys.exits on parsing errors. We want to wrap
# that in our own exception.
- def parser_exit(self: Any, msg: str) -> "NoReturn":
+ def parser_exit(self: Any, msg: str) -> NoReturn:
raise OptionParsingError(msg)
# NOTE: mypy disallows assigning to a method
@@ -500,7 +497,7 @@ def join_lines(lines_enum: ReqFileLines) -> ReqFileLines:
comments). The joined line takes on the index of the first line.
"""
primary_line_number = None
- new_line: List[str] = []
+ new_line: list[str] = []
for line_number, line in lines_enum:
if not line.endswith("\\") or COMMENT_RE.match(line):
if COMMENT_RE.match(line):
@@ -564,7 +561,7 @@ def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines:
yield line_number, line
-def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]:
+def get_file_content(url: str, session: PipSession) -> tuple[str, str]:
"""Gets the content of a file; it may be a filename, file: URL, or
http: URL. Returns (location, content). Content is unicode.
Respects # -*- coding: declarations on the retrieved files.
diff --git a/contrib/python/pip/pip/_internal/req/req_install.py b/contrib/python/pip/pip/_internal/req/req_install.py
index 99d6936b735..c9f6bff17e8 100644
--- a/contrib/python/pip/pip/_internal/req/req_install.py
+++ b/contrib/python/pip/pip/_internal/req/req_install.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import functools
import logging
import os
@@ -5,9 +7,10 @@ import shutil
import sys
import uuid
import zipfile
+from collections.abc import Collection, Iterable, Sequence
from optparse import Values
from pathlib import Path
-from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
+from typing import Any
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import Requirement
@@ -71,17 +74,17 @@ class InstallRequirement:
def __init__(
self,
- req: Optional[Requirement],
- comes_from: Optional[Union[str, "InstallRequirement"]],
+ req: Requirement | None,
+ comes_from: str | InstallRequirement | None,
editable: bool = False,
- link: Optional[Link] = None,
- markers: Optional[Marker] = None,
- use_pep517: Optional[bool] = None,
+ link: Link | None = None,
+ markers: Marker | None = None,
+ use_pep517: bool | None = None,
isolated: bool = False,
*,
- global_options: Optional[List[str]] = None,
- hash_options: Optional[Dict[str, List[str]]] = None,
- config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
+ global_options: list[str] | None = None,
+ hash_options: dict[str, list[str]] | None = None,
+ config_settings: dict[str, str | list[str]] | None = None,
constraint: bool = False,
extras: Collection[str] = (),
user_supplied: bool = False,
@@ -99,7 +102,7 @@ class InstallRequirement:
# populating source_dir is done by the RequirementPreparer. Note this
# is not necessarily the directory where pyproject.toml or setup.py is
# located - that one is obtained via unpacked_source_directory.
- self.source_dir: Optional[str] = None
+ self.source_dir: str | None = None
if self.editable:
assert link
if link.is_file:
@@ -115,14 +118,14 @@ class InstallRequirement:
# When this InstallRequirement is a wheel obtained from the cache of locally
# built wheels, this is the source link corresponding to the cache entry, which
# was used to download and build the cached wheel.
- self.cached_wheel_source_link: Optional[Link] = None
+ self.cached_wheel_source_link: Link | None = None
# Information about the location of the artifact that was downloaded . This
# property is guaranteed to be set in resolver results.
- self.download_info: Optional[DirectUrl] = None
+ self.download_info: DirectUrl | None = None
# Path to any downloaded or already-existing package.
- self.local_file_path: Optional[str] = None
+ self.local_file_path: str | None = None
if self.link and self.link.is_file:
self.local_file_path = self.link.file_path
@@ -137,14 +140,14 @@ class InstallRequirement:
self.markers = markers
# This holds the Distribution object if this requirement is already installed.
- self.satisfied_by: Optional[BaseDistribution] = None
+ self.satisfied_by: BaseDistribution | None = None
# Whether the installation process should try to uninstall an existing
# distribution before installing this requirement.
self.should_reinstall = False
# Temporary build location
- self._temp_build_dir: Optional[TempDirectory] = None
+ self._temp_build_dir: TempDirectory | None = None
# Set to True after successful installation
- self.install_succeeded: Optional[bool] = None
+ self.install_succeeded: bool | None = None
# Supplied options
self.global_options = global_options if global_options else []
self.hash_options = hash_options if hash_options else {}
@@ -163,16 +166,16 @@ class InstallRequirement:
# gets stored. We need this to pass to build_wheel, so the backend
# can ensure that the wheel matches the metadata (see the PEP for
# details).
- self.metadata_directory: Optional[str] = None
+ self.metadata_directory: str | None = None
# The static build requirements (from pyproject.toml)
- self.pyproject_requires: Optional[List[str]] = None
+ self.pyproject_requires: list[str] | None = None
# Build requirements that we will check are available
- self.requirements_to_check: List[str] = []
+ self.requirements_to_check: list[str] = []
# The PEP 517 backend we should use to build the project
- self.pep517_backend: Optional[BuildBackendHookCaller] = None
+ self.pep517_backend: BuildBackendHookCaller | None = None
# Are we using PEP 517 for this requirement?
# After pyproject.toml has been loaded, the only valid values are True
@@ -195,7 +198,7 @@ class InstallRequirement:
self.needs_more_preparation = False
# This requirement needs to be unpacked before it can be installed.
- self._archive_source: Optional[Path] = None
+ self._archive_source: Path | None = None
def __str__(self) -> str:
if self.req:
@@ -214,7 +217,7 @@ class InstallRequirement:
s += f" in {location}"
if self.comes_from:
if isinstance(self.comes_from, str):
- comes_from: Optional[str] = self.comes_from
+ comes_from: str | None = self.comes_from
else:
comes_from = self.comes_from.from_path()
if comes_from:
@@ -240,7 +243,7 @@ class InstallRequirement:
# Things that are valid for all kinds of requirements?
@property
- def name(self) -> Optional[str]:
+ def name(self) -> str | None:
if self.req is None:
return None
return self.req.name
@@ -277,7 +280,7 @@ class InstallRequirement:
specifiers = self.req.specifier
return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
- def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
+ def match_markers(self, extras_requested: Iterable[str] | None = None) -> bool:
if not extras_requested:
# Provide an extra to safely evaluate the markers
# without matching any extra
@@ -326,13 +329,13 @@ class InstallRequirement:
good_hashes.setdefault(link.hash_name, []).append(link.hash)
return Hashes(good_hashes)
- def from_path(self) -> Optional[str]:
+ def from_path(self) -> str | None:
"""Format a nice indicator to show where this "comes from" """
if self.req is None:
return None
s = str(self.req)
if self.comes_from:
- comes_from: Optional[str]
+ comes_from: str | None
if isinstance(self.comes_from, str):
comes_from = self.comes_from
else:
@@ -699,7 +702,7 @@ class InstallRequirement:
# Top-level Actions
def uninstall(
self, auto_confirm: bool = False, verbose: bool = False
- ) -> Optional[UninstallPathSet]:
+ ) -> UninstallPathSet | None:
"""
Uninstall the distribution currently satisfying this requirement.
@@ -737,7 +740,7 @@ class InstallRequirement:
name = _clean_zip_name(path, rootdir)
return self.req.name + "/" + name
- def archive(self, build_dir: Optional[str]) -> None:
+ def archive(self, build_dir: str | None) -> None:
"""Saves archive to provided build_dir.
Used for saving downloaded VCS requirements as part of `pip download`.
@@ -806,10 +809,10 @@ class InstallRequirement:
def install(
self,
- global_options: Optional[Sequence[str]] = None,
- root: Optional[str] = None,
- home: Optional[str] = None,
- prefix: Optional[str] = None,
+ global_options: Sequence[str] | None = None,
+ root: str | None = None,
+ home: str | None = None,
+ prefix: str | None = None,
warn_script_location: bool = True,
use_user_site: bool = False,
pycompile: bool = True,
@@ -905,7 +908,7 @@ def check_invalid_constraint_type(req: InstallRequirement) -> str:
return problem
-def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool:
+def _has_option(options: Values, reqs: list[InstallRequirement], option: str) -> bool:
if getattr(options, option, None):
return True
for req in reqs:
@@ -916,7 +919,7 @@ def _has_option(options: Values, reqs: List[InstallRequirement], option: str) ->
def check_legacy_setup_py_options(
options: Values,
- reqs: List[InstallRequirement],
+ reqs: list[InstallRequirement],
) -> None:
has_build_options = _has_option(options, reqs, "build_options")
has_global_options = _has_option(options, reqs, "global_options")
diff --git a/contrib/python/pip/pip/_internal/req/req_set.py b/contrib/python/pip/pip/_internal/req/req_set.py
index ec7a6e07a25..3451b24f27b 100644
--- a/contrib/python/pip/pip/_internal/req/req_set.py
+++ b/contrib/python/pip/pip/_internal/req/req_set.py
@@ -1,6 +1,5 @@
import logging
from collections import OrderedDict
-from typing import Dict, List
from pip._vendor.packaging.utils import canonicalize_name
@@ -13,10 +12,10 @@ class RequirementSet:
def __init__(self, check_supported_wheels: bool = True) -> None:
"""Create a RequirementSet."""
- self.requirements: Dict[str, InstallRequirement] = OrderedDict()
+ self.requirements: dict[str, InstallRequirement] = OrderedDict()
self.check_supported_wheels = check_supported_wheels
- self.unnamed_requirements: List[InstallRequirement] = []
+ self.unnamed_requirements: list[InstallRequirement] = []
def __str__(self) -> str:
requirements = sorted(
@@ -65,11 +64,11 @@ class RequirementSet:
raise KeyError(f"No project with the name {name!r}")
@property
- def all_requirements(self) -> List[InstallRequirement]:
+ def all_requirements(self) -> list[InstallRequirement]:
return self.unnamed_requirements + list(self.requirements.values())
@property
- def requirements_to_install(self) -> List[InstallRequirement]:
+ def requirements_to_install(self) -> list[InstallRequirement]:
"""Return the list of requirements that need to be installed.
TODO remove this property together with the legacy resolver, since the new
diff --git a/contrib/python/pip/pip/_internal/req/req_uninstall.py b/contrib/python/pip/pip/_internal/req/req_uninstall.py
index a41082317d1..3f3dde2fdd9 100644
--- a/contrib/python/pip/pip/_internal/req/req_uninstall.py
+++ b/contrib/python/pip/pip/_internal/req/req_uninstall.py
@@ -1,9 +1,12 @@
+from __future__ import annotations
+
import functools
import os
import sys
import sysconfig
+from collections.abc import Generator, Iterable
from importlib.util import cache_from_source
-from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple
+from typing import Any, Callable
from pip._internal.exceptions import LegacyDistutilsInstall, UninstallMissingRecord
from pip._internal.locations import get_bin_prefix, get_bin_user
@@ -42,7 +45,7 @@ def _unique(
) -> Callable[..., Generator[Any, None, None]]:
@functools.wraps(fn)
def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]:
- seen: Set[Any] = set()
+ seen: set[Any] = set()
for item in fn(*args, **kw):
if item not in seen:
seen.add(item)
@@ -85,14 +88,14 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
yield path
-def compact(paths: Iterable[str]) -> Set[str]:
+def compact(paths: Iterable[str]) -> set[str]:
"""Compact a path set to contain the minimal number of paths
necessary to contain all paths in the set. If /a/path/ and
/a/path/to/a/file.txt are both in the set, leave only the
shorter path."""
sep = os.path.sep
- short_paths: Set[str] = set()
+ short_paths: set[str] = set()
for path in sorted(paths, key=len):
should_skip = any(
path.startswith(shortpath.rstrip("*"))
@@ -104,7 +107,7 @@ def compact(paths: Iterable[str]) -> Set[str]:
return short_paths
-def compress_for_rename(paths: Iterable[str]) -> Set[str]:
+def compress_for_rename(paths: Iterable[str]) -> set[str]:
"""Returns a set containing the paths that need to be renamed.
This set may include directories when the original sequence of paths
@@ -113,7 +116,7 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
case_map = {os.path.normcase(p): p for p in paths}
remaining = set(case_map)
unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len)
- wildcards: Set[str] = set()
+ wildcards: set[str] = set()
def norm_join(*a: str) -> str:
return os.path.normcase(os.path.join(*a))
@@ -123,8 +126,8 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
# This directory has already been handled.
continue
- all_files: Set[str] = set()
- all_subdirs: Set[str] = set()
+ all_files: set[str] = set()
+ all_subdirs: set[str] = set()
for dirname, subdirs, files in os.walk(root):
all_subdirs.update(norm_join(root, dirname, d) for d in subdirs)
all_files.update(norm_join(root, dirname, f) for f in files)
@@ -138,7 +141,7 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
return set(map(case_map.__getitem__, remaining)) | wildcards
-def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]:
+def compress_for_output_listing(paths: Iterable[str]) -> tuple[set[str], set[str]]:
"""Returns a tuple of 2 sets of which paths to display to user
The first set contains paths that would be deleted. Files of a package
@@ -194,10 +197,10 @@ class StashedUninstallPathSet:
def __init__(self) -> None:
# Mapping from source file root to [Adjacent]TempDirectory
# for files under that directory.
- self._save_dirs: Dict[str, TempDirectory] = {}
+ self._save_dirs: dict[str, TempDirectory] = {}
# (old path, new path) tuples for each move that may need
# to be undone.
- self._moves: List[Tuple[str, str]] = []
+ self._moves: list[tuple[str, str]] = []
def _get_directory_stash(self, path: str) -> str:
"""Stashes a directory.
@@ -297,9 +300,9 @@ class UninstallPathSet:
requirement."""
def __init__(self, dist: BaseDistribution) -> None:
- self._paths: Set[str] = set()
- self._refuse: Set[str] = set()
- self._pth: Dict[str, UninstallPthEntries] = {}
+ self._paths: set[str] = set()
+ self._refuse: set[str] = set()
+ self._pth: dict[str, UninstallPthEntries] = {}
self._dist = dist
self._moved_paths = StashedUninstallPathSet()
# Create local cache of normalize_path results. Creating an UninstallPathSet
@@ -421,7 +424,7 @@ class UninstallPathSet:
self._moved_paths.commit()
@classmethod
- def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet":
+ def from_dist(cls, dist: BaseDistribution) -> UninstallPathSet:
dist_location = dist.location
info_location = dist.info_location
if dist_location is None:
@@ -581,8 +584,8 @@ class UninstallPathSet:
class UninstallPthEntries:
def __init__(self, pth_file: str) -> None:
self.file = pth_file
- self.entries: Set[str] = set()
- self._saved_lines: Optional[List[bytes]] = None
+ self.entries: set[str] = set()
+ self._saved_lines: list[bytes] | None = None
def add(self, entry: str) -> None:
entry = os.path.normcase(entry)
diff --git a/contrib/python/pip/pip/_internal/resolution/base.py b/contrib/python/pip/pip/_internal/resolution/base.py
index 42dade18c1e..5ec4d96aa78 100644
--- a/contrib/python/pip/pip/_internal/resolution/base.py
+++ b/contrib/python/pip/pip/_internal/resolution/base.py
@@ -1,4 +1,4 @@
-from typing import Callable, List, Optional
+from typing import Callable, Optional
from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.req_set import RequirementSet
@@ -10,11 +10,11 @@ InstallRequirementProvider = Callable[
class BaseResolver:
def resolve(
- self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
+ self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
) -> RequirementSet:
raise NotImplementedError()
def get_installation_order(
self, req_set: RequirementSet
- ) -> List[InstallRequirement]:
+ ) -> list[InstallRequirement]:
raise NotImplementedError()
diff --git a/contrib/python/pip/pip/_internal/resolution/legacy/resolver.py b/contrib/python/pip/pip/_internal/resolution/legacy/resolver.py
index 1dd0d7041bb..33a4fdc3bbc 100644
--- a/contrib/python/pip/pip/_internal/resolution/legacy/resolver.py
+++ b/contrib/python/pip/pip/_internal/resolution/legacy/resolver.py
@@ -10,11 +10,14 @@ for sub-dependencies
a. "first found, wins" (where the order is breadth first)
"""
+from __future__ import annotations
+
import logging
import sys
from collections import defaultdict
+from collections.abc import Iterable
from itertools import chain
-from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
+from typing import Optional
from pip._vendor.packaging import specifiers
from pip._vendor.packaging.requirements import Requirement
@@ -49,12 +52,12 @@ from pip._internal.utils.packaging import check_requires_python
logger = logging.getLogger(__name__)
-DiscoveredDependencies = DefaultDict[Optional[str], List[InstallRequirement]]
+DiscoveredDependencies = defaultdict[Optional[str], list[InstallRequirement]]
def _check_dist_requires_python(
dist: BaseDistribution,
- version_info: Tuple[int, int, int],
+ version_info: tuple[int, int, int],
ignore_requires_python: bool = False,
) -> None:
"""
@@ -117,7 +120,7 @@ class Resolver(BaseResolver):
self,
preparer: RequirementPreparer,
finder: PackageFinder,
- wheel_cache: Optional[WheelCache],
+ wheel_cache: WheelCache | None,
make_install_req: InstallRequirementProvider,
use_user_site: bool,
ignore_dependencies: bool,
@@ -125,7 +128,7 @@ class Resolver(BaseResolver):
ignore_requires_python: bool,
force_reinstall: bool,
upgrade_strategy: str,
- py_version_info: Optional[Tuple[int, ...]] = None,
+ py_version_info: tuple[int, ...] | None = None,
) -> None:
super().__init__()
assert upgrade_strategy in self._allowed_strategies
@@ -152,7 +155,7 @@ class Resolver(BaseResolver):
self._discovered_dependencies: DiscoveredDependencies = defaultdict(list)
def resolve(
- self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
+ self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
) -> RequirementSet:
"""Resolve what operations need to be done
@@ -174,7 +177,7 @@ class Resolver(BaseResolver):
# exceptions cannot be checked ahead of time, because
# _populate_link() needs to be called before we can make decisions
# based on link type.
- discovered_reqs: List[InstallRequirement] = []
+ discovered_reqs: list[InstallRequirement] = []
hash_errors = HashErrors()
for req in chain(requirement_set.all_requirements, discovered_reqs):
try:
@@ -192,9 +195,9 @@ class Resolver(BaseResolver):
self,
requirement_set: RequirementSet,
install_req: InstallRequirement,
- parent_req_name: Optional[str] = None,
- extras_requested: Optional[Iterable[str]] = None,
- ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
+ parent_req_name: str | None = None,
+ extras_requested: Iterable[str] | None = None,
+ ) -> tuple[list[InstallRequirement], InstallRequirement | None]:
"""Add install_req as a requirement to install.
:param parent_req_name: The name of the requirement that needed this
@@ -242,8 +245,8 @@ class Resolver(BaseResolver):
return [install_req], None
try:
- existing_req: Optional[InstallRequirement] = (
- requirement_set.get_requirement(install_req.name)
+ existing_req: InstallRequirement | None = requirement_set.get_requirement(
+ install_req.name
)
except KeyError:
existing_req = None
@@ -323,9 +326,7 @@ class Resolver(BaseResolver):
req.should_reinstall = True
req.satisfied_by = None
- def _check_skip_installed(
- self, req_to_install: InstallRequirement
- ) -> Optional[str]:
+ def _check_skip_installed(self, req_to_install: InstallRequirement) -> str | None:
"""Check if req_to_install should be skipped.
This will check if the req is installed, and whether we should upgrade
@@ -377,7 +378,7 @@ class Resolver(BaseResolver):
self._set_req_to_reinstall(req_to_install)
return None
- def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]:
+ def _find_requirement_link(self, req: InstallRequirement) -> Link | None:
upgrade = self._is_upgrade_allowed(req)
best_candidate = self.finder.find_requirement(req, upgrade)
if not best_candidate:
@@ -488,7 +489,7 @@ class Resolver(BaseResolver):
self,
requirement_set: RequirementSet,
req_to_install: InstallRequirement,
- ) -> List[InstallRequirement]:
+ ) -> list[InstallRequirement]:
"""Prepare a single requirements file.
:return: A list of additional InstallRequirements to also install.
@@ -511,7 +512,7 @@ class Resolver(BaseResolver):
ignore_requires_python=self.ignore_requires_python,
)
- more_reqs: List[InstallRequirement] = []
+ more_reqs: list[InstallRequirement] = []
def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
# This idiosyncratically converts the Requirement to str and let
@@ -569,7 +570,7 @@ class Resolver(BaseResolver):
def get_installation_order(
self, req_set: RequirementSet
- ) -> List[InstallRequirement]:
+ ) -> list[InstallRequirement]:
"""Create the installation order.
The installation order is topological - requirements are installed
@@ -580,7 +581,7 @@ class Resolver(BaseResolver):
# installs the user specified things in the order given, except when
# dependencies must come earlier to achieve topological order.
order = []
- ordered_reqs: Set[InstallRequirement] = set()
+ ordered_reqs: set[InstallRequirement] = set()
def schedule(req: InstallRequirement) -> None:
if req.satisfied_by or req in ordered_reqs:
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/base.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/base.py
index 0f31dc9b307..03877b6c2dd 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/base.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/base.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
+from collections.abc import Iterable
from dataclasses import dataclass
-from typing import FrozenSet, Iterable, Optional, Tuple
+from typing import Optional
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import NormalizedName
@@ -9,10 +12,10 @@ from pip._internal.models.link import Link, links_equivalent
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.hashes import Hashes
-CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]]
+CandidateLookup = tuple[Optional["Candidate"], Optional[InstallRequirement]]
-def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> str:
+def format_name(project: NormalizedName, extras: frozenset[NormalizedName]) -> str:
if not extras:
return project
extras_expr = ",".join(sorted(extras))
@@ -23,21 +26,21 @@ def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> s
class Constraint:
specifier: SpecifierSet
hashes: Hashes
- links: FrozenSet[Link]
+ links: frozenset[Link]
@classmethod
- def empty(cls) -> "Constraint":
+ def empty(cls) -> Constraint:
return Constraint(SpecifierSet(), Hashes(), frozenset())
@classmethod
- def from_ireq(cls, ireq: InstallRequirement) -> "Constraint":
+ def from_ireq(cls, ireq: InstallRequirement) -> Constraint:
links = frozenset([ireq.link]) if ireq.link else frozenset()
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links)
def __bool__(self) -> bool:
return bool(self.specifier) or bool(self.hashes) or bool(self.links)
- def __and__(self, other: InstallRequirement) -> "Constraint":
+ def __and__(self, other: InstallRequirement) -> Constraint:
if not isinstance(other, InstallRequirement):
return NotImplemented
specifier = self.specifier & other.specifier
@@ -47,7 +50,7 @@ class Constraint:
links = links.union([other.link])
return Constraint(specifier, hashes, links)
- def is_satisfied_by(self, candidate: "Candidate") -> bool:
+ def is_satisfied_by(self, candidate: Candidate) -> bool:
# Reject if there are any mismatched URL constraints on this package.
if self.links and not all(_match_link(link, candidate) for link in self.links):
return False
@@ -77,7 +80,7 @@ class Requirement:
"""
raise NotImplementedError("Subclass should override")
- def is_satisfied_by(self, candidate: "Candidate") -> bool:
+ def is_satisfied_by(self, candidate: Candidate) -> bool:
return False
def get_candidate_lookup(self) -> CandidateLookup:
@@ -87,7 +90,7 @@ class Requirement:
raise NotImplementedError("Subclass should override")
-def _match_link(link: Link, candidate: "Candidate") -> bool:
+def _match_link(link: Link, candidate: Candidate) -> bool:
if candidate.source_link:
return links_equivalent(link, candidate.source_link)
return False
@@ -126,13 +129,13 @@ class Candidate:
raise NotImplementedError("Override in subclass")
@property
- def source_link(self) -> Optional[Link]:
+ def source_link(self) -> Link | None:
raise NotImplementedError("Override in subclass")
- def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
+ def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
raise NotImplementedError("Override in subclass")
- def get_install_requirement(self) -> Optional[InstallRequirement]:
+ def get_install_requirement(self) -> InstallRequirement | None:
raise NotImplementedError("Override in subclass")
def format_for_error(self) -> str:
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/candidates.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/candidates.py
index d976026ac18..a8315349791 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/candidates.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/candidates.py
@@ -1,6 +1,9 @@
+from __future__ import annotations
+
import logging
import sys
-from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
+from collections.abc import Iterable
+from typing import TYPE_CHECKING, Any, Union, cast
from pip._vendor.packaging.requirements import InvalidRequirement
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
@@ -41,7 +44,7 @@ BaseCandidate = Union[
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
-def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
+def as_base_candidate(candidate: Candidate) -> BaseCandidate | None:
"""The runtime version of BaseCandidate."""
base_candidate_classes = (
AlreadyInstalledCandidate,
@@ -146,9 +149,9 @@ class _InstallRequirementBackedCandidate(Candidate):
link: Link,
source_link: Link,
ireq: InstallRequirement,
- factory: "Factory",
- name: Optional[NormalizedName] = None,
- version: Optional[Version] = None,
+ factory: Factory,
+ name: NormalizedName | None = None,
+ version: Version | None = None,
) -> None:
self._link = link
self._source_link = source_link
@@ -157,7 +160,7 @@ class _InstallRequirementBackedCandidate(Candidate):
self._name = name
self._version = version
self.dist = self._prepare()
- self._hash: Optional[int] = None
+ self._hash: int | None = None
def __str__(self) -> str:
return f"{self.name} {self.version}"
@@ -178,7 +181,7 @@ class _InstallRequirementBackedCandidate(Candidate):
return False
@property
- def source_link(self) -> Optional[Link]:
+ def source_link(self) -> Link | None:
return self._source_link
@property
@@ -248,7 +251,7 @@ class _InstallRequirementBackedCandidate(Candidate):
self._check_metadata_consistency(dist)
return dist
- def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
+ def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
# Emit the Requires-Python requirement first to fail fast on
# unsupported candidates and avoid pointless downloads/preparation.
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
@@ -256,7 +259,7 @@ class _InstallRequirementBackedCandidate(Candidate):
for r in requires:
yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
- def get_install_requirement(self) -> Optional[InstallRequirement]:
+ def get_install_requirement(self) -> InstallRequirement | None:
return self._ireq
@@ -267,9 +270,9 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
self,
link: Link,
template: InstallRequirement,
- factory: "Factory",
- name: Optional[NormalizedName] = None,
- version: Optional[Version] = None,
+ factory: Factory,
+ name: NormalizedName | None = None,
+ version: Version | None = None,
) -> None:
source_link = link
cache_entry = factory.get_wheel_cache_entry(source_link, name)
@@ -324,9 +327,9 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
self,
link: Link,
template: InstallRequirement,
- factory: "Factory",
- name: Optional[NormalizedName] = None,
- version: Optional[Version] = None,
+ factory: Factory,
+ name: NormalizedName | None = None,
+ version: Version | None = None,
) -> None:
super().__init__(
link=link,
@@ -349,7 +352,7 @@ class AlreadyInstalledCandidate(Candidate):
self,
dist: BaseDistribution,
template: InstallRequirement,
- factory: "Factory",
+ factory: Factory,
) -> None:
self.dist = dist
self._ireq = _make_install_req_from_dist(dist, template)
@@ -398,7 +401,7 @@ class AlreadyInstalledCandidate(Candidate):
def format_for_error(self) -> str:
return f"{self.name} {self.version} (Installed)"
- def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
+ def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
if not with_requires:
return
@@ -408,7 +411,7 @@ class AlreadyInstalledCandidate(Candidate):
except InvalidRequirement as exc:
raise InvalidInstalledPackage(dist=self.dist, invalid_exc=exc) from None
- def get_install_requirement(self) -> Optional[InstallRequirement]:
+ def get_install_requirement(self) -> InstallRequirement | None:
return None
@@ -440,9 +443,9 @@ class ExtrasCandidate(Candidate):
def __init__(
self,
base: BaseCandidate,
- extras: FrozenSet[str],
+ extras: frozenset[str],
*,
- comes_from: Optional[InstallRequirement] = None,
+ comes_from: InstallRequirement | None = None,
) -> None:
"""
:param comes_from: the InstallRequirement that led to this candidate if it
@@ -498,10 +501,10 @@ class ExtrasCandidate(Candidate):
return self.base.is_editable
@property
- def source_link(self) -> Optional[Link]:
+ def source_link(self) -> Link | None:
return self.base.source_link
- def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
+ def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
factory = self.base._factory
# Add a dependency on the exact base
@@ -529,7 +532,7 @@ class ExtrasCandidate(Candidate):
valid_extras,
)
- def get_install_requirement(self) -> Optional[InstallRequirement]:
+ def get_install_requirement(self) -> InstallRequirement | None:
# We don't return anything here, because we always
# depend on the base candidate, and we'll get the
# install requirement from that.
@@ -540,7 +543,7 @@ class RequiresPythonCandidate(Candidate):
is_installed = False
source_link = None
- def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
+ def __init__(self, py_version_info: tuple[int, ...] | None) -> None:
if py_version_info is not None:
version_info = normalize_version_info(py_version_info)
else:
@@ -572,8 +575,8 @@ class RequiresPythonCandidate(Candidate):
def format_for_error(self) -> str:
return f"Python {self.version}"
- def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
+ def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
return ()
- def get_install_requirement(self) -> Optional[InstallRequirement]:
+ def get_install_requirement(self) -> InstallRequirement | None:
return None
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/factory.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/factory.py
index 55c11b29158..f23e4cd6258 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/factory.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/factory.py
@@ -1,21 +1,14 @@
+from __future__ import annotations
+
import contextlib
import functools
import logging
+from collections.abc import Iterable, Iterator, Mapping, Sequence
from typing import (
TYPE_CHECKING,
Callable,
- Dict,
- FrozenSet,
- Iterable,
- Iterator,
- List,
- Mapping,
NamedTuple,
- Optional,
Protocol,
- Sequence,
- Set,
- Tuple,
TypeVar,
cast,
)
@@ -84,13 +77,13 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
C = TypeVar("C")
-Cache = Dict[Link, C]
+Cache = dict[Link, C]
class CollectedRootRequirements(NamedTuple):
- requirements: List[Requirement]
- constraints: Dict[str, Constraint]
- user_requested: Dict[str, int]
+ requirements: list[Requirement]
+ constraints: dict[str, Constraint]
+ user_requested: dict[str, int]
class Factory:
@@ -99,12 +92,12 @@ class Factory:
finder: PackageFinder,
preparer: RequirementPreparer,
make_install_req: InstallRequirementProvider,
- wheel_cache: Optional[WheelCache],
+ wheel_cache: WheelCache | None,
use_user_site: bool,
force_reinstall: bool,
ignore_installed: bool,
ignore_requires_python: bool,
- py_version_info: Optional[Tuple[int, ...]] = None,
+ py_version_info: tuple[int, ...] | None = None,
) -> None:
self._finder = finder
self.preparer = preparer
@@ -118,9 +111,9 @@ class Factory:
self._build_failures: Cache[InstallationError] = {}
self._link_candidate_cache: Cache[LinkCandidate] = {}
self._editable_candidate_cache: Cache[EditableCandidate] = {}
- self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {}
- self._extras_candidate_cache: Dict[
- Tuple[int, FrozenSet[NormalizedName]], ExtrasCandidate
+ self._installed_candidate_cache: dict[str, AlreadyInstalledCandidate] = {}
+ self._extras_candidate_cache: dict[
+ tuple[int, frozenset[NormalizedName]], ExtrasCandidate
] = {}
self._supported_tags_cache = get_supported()
@@ -149,9 +142,9 @@ class Factory:
def _make_extras_candidate(
self,
base: BaseCandidate,
- extras: FrozenSet[str],
+ extras: frozenset[str],
*,
- comes_from: Optional[InstallRequirement] = None,
+ comes_from: InstallRequirement | None = None,
) -> ExtrasCandidate:
cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras))
try:
@@ -164,7 +157,7 @@ class Factory:
def _make_candidate_from_dist(
self,
dist: BaseDistribution,
- extras: FrozenSet[str],
+ extras: frozenset[str],
template: InstallRequirement,
) -> Candidate:
try:
@@ -179,12 +172,12 @@ class Factory:
def _make_candidate_from_link(
self,
link: Link,
- extras: FrozenSet[str],
+ extras: frozenset[str],
template: InstallRequirement,
- name: Optional[NormalizedName],
- version: Optional[Version],
- ) -> Optional[Candidate]:
- base: Optional[BaseCandidate] = self._make_base_candidate_from_link(
+ name: NormalizedName | None,
+ version: Version | None,
+ ) -> Candidate | None:
+ base: BaseCandidate | None = self._make_base_candidate_from_link(
link, template, name, version
)
if not extras or base is None:
@@ -195,9 +188,9 @@ class Factory:
self,
link: Link,
template: InstallRequirement,
- name: Optional[NormalizedName],
- version: Optional[Version],
- ) -> Optional[BaseCandidate]:
+ name: NormalizedName | None,
+ version: Version | None,
+ ) -> BaseCandidate | None:
# TODO: Check already installed candidate, and use it if the link and
# editable flag match.
@@ -254,7 +247,7 @@ class Factory:
specifier: SpecifierSet,
hashes: Hashes,
prefers_installed: bool,
- incompatible_ids: Set[int],
+ incompatible_ids: set[int],
) -> Iterable[Candidate]:
if not ireqs:
return ()
@@ -267,14 +260,14 @@ class Factory:
assert template.req, "Candidates found on index must be PEP 508"
name = canonicalize_name(template.req.name)
- extras: FrozenSet[str] = frozenset()
+ extras: frozenset[str] = frozenset()
for ireq in ireqs:
assert ireq.req, "Candidates found on index must be PEP 508"
specifier &= ireq.req.specifier
hashes &= ireq.hashes(trust_internet=False)
extras |= frozenset(ireq.extras)
- def _get_installed_candidate() -> Optional[Candidate]:
+ def _get_installed_candidate() -> Candidate | None:
"""Get the candidate for the currently-installed version."""
# If --force-reinstall is set, we want the version from the index
# instead, so we "pretend" there is nothing installed.
@@ -353,7 +346,7 @@ class Factory:
def _iter_explicit_candidates_from_base(
self,
base_requirements: Iterable[Requirement],
- extras: FrozenSet[str],
+ extras: frozenset[str],
) -> Iterator[Candidate]:
"""Produce explicit candidates from the base given an extra-ed package.
@@ -404,8 +397,8 @@ class Factory:
is_satisfied_by: Callable[[Requirement, Candidate], bool],
) -> Iterable[Candidate]:
# Collect basic lookup information from the requirements.
- explicit_candidates: Set[Candidate] = set()
- ireqs: List[InstallRequirement] = []
+ explicit_candidates: set[Candidate] = set()
+ ireqs: list[InstallRequirement] = []
for req in requirements[identifier]:
cand, ireq = req.get_candidate_lookup()
if cand is not None:
@@ -524,7 +517,7 @@ class Factory:
)
def collect_root_requirements(
- self, root_ireqs: List[InstallRequirement]
+ self, root_ireqs: list[InstallRequirement]
) -> CollectedRootRequirements:
collected = CollectedRootRequirements([], {}, {})
for i, ireq in enumerate(root_ireqs):
@@ -573,7 +566,7 @@ class Factory:
def make_requirements_from_spec(
self,
specifier: str,
- comes_from: Optional[InstallRequirement],
+ comes_from: InstallRequirement | None,
requested_extras: Iterable[str] = (),
) -> Iterator[Requirement]:
"""
@@ -591,7 +584,7 @@ class Factory:
def make_requires_python_requirement(
self,
specifier: SpecifierSet,
- ) -> Optional[Requirement]:
+ ) -> Requirement | None:
if self._ignore_requires_python:
return None
# Don't bother creating a dependency for an empty Requires-Python.
@@ -599,9 +592,7 @@ class Factory:
return None
return RequiresPythonRequirement(specifier, self._python_candidate)
- def get_wheel_cache_entry(
- self, link: Link, name: Optional[str]
- ) -> Optional[CacheEntry]:
+ def get_wheel_cache_entry(self, link: Link, name: str | None) -> CacheEntry | None:
"""Look up the link in the wheel cache.
If ``preparer.require_hashes`` is True, don't use the wheel cache,
@@ -618,7 +609,7 @@ class Factory:
supported_tags=self._supported_tags_cache,
)
- def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
+ def get_dist_to_uninstall(self, candidate: Candidate) -> BaseDistribution | None:
# TODO: Are there more cases this needs to return True? Editable?
dist = self._installed_dists.get(candidate.project_name)
if dist is None: # Not installed, no uninstallation required.
@@ -647,7 +638,7 @@ class Factory:
return None
def _report_requires_python_error(
- self, causes: Sequence["ConflictCause"]
+ self, causes: Sequence[ConflictCause]
) -> UnsupportedPythonVersion:
assert causes, "Requires-Python error reported with no cause"
@@ -669,7 +660,7 @@ class Factory:
return UnsupportedPythonVersion(message)
def _report_single_requirement_conflict(
- self, req: Requirement, parent: Optional[Candidate]
+ self, req: Requirement, parent: Candidate | None
) -> DistributionNotFound:
if parent is None:
req_disp = str(req)
@@ -679,8 +670,8 @@ class Factory:
cands = self._finder.find_all_candidates(req.project_name)
skipped_by_requires_python = self._finder.requires_python_skipped_reasons()
- versions_set: Set[Version] = set()
- yanked_versions_set: Set[Version] = set()
+ versions_set: set[Version] = set()
+ yanked_versions_set: set[Version] = set()
for c in cands:
is_yanked = c.link.is_yanked if c.link else False
if is_yanked:
@@ -722,8 +713,8 @@ class Factory:
def get_installation_error(
self,
- e: "ResolutionImpossible[Requirement, Candidate]",
- constraints: Dict[str, Constraint],
+ e: ResolutionImpossible[Requirement, Candidate],
+ constraints: dict[str, Constraint],
) -> InstallationError:
assert e.causes, "Installation error reported with no cause"
@@ -756,7 +747,7 @@ class Factory:
# satisfied at once.
# A couple of formatting helpers
- def text_join(parts: List[str]) -> str:
+ def text_join(parts: list[str]) -> str:
if len(parts) == 1:
return parts[0]
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/found_candidates.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/found_candidates.py
index 3a9c2ed723d..f60653d21d4 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/found_candidates.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/found_candidates.py
@@ -8,9 +8,11 @@ absolutely need, and not "download the world" when we only need one version of
something.
"""
+from __future__ import annotations
+
import logging
-from collections.abc import Sequence
-from typing import Any, Callable, Iterator, Optional, Set, Tuple
+from collections.abc import Iterator, Sequence
+from typing import Any, Callable, Optional
from pip._vendor.packaging.version import _BaseVersion
@@ -20,7 +22,7 @@ from .base import Candidate
logger = logging.getLogger(__name__)
-IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
+IndexCandidateInfo = tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
@@ -29,7 +31,7 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
This iterator is used when the package is not already installed. Candidates
from index come later in their normal ordering.
"""
- versions_found: Set[_BaseVersion] = set()
+ versions_found: set[_BaseVersion] = set()
for version, func in infos:
if version in versions_found:
continue
@@ -65,7 +67,7 @@ def _iter_built_with_prepended(
normal ordering, except skipped when the version is already installed.
"""
yield installed
- versions_found: Set[_BaseVersion] = {installed.version}
+ versions_found: set[_BaseVersion] = {installed.version}
for version, func in infos:
if version in versions_found:
continue
@@ -89,7 +91,7 @@ def _iter_built_with_inserted(
the installed candidate exactly once before we start yielding older or
equivalent candidates, or after all other candidates if they are all newer.
"""
- versions_found: Set[_BaseVersion] = set()
+ versions_found: set[_BaseVersion] = set()
for version, func in infos:
if version in versions_found:
continue
@@ -120,15 +122,15 @@ class FoundCandidates(Sequence[Candidate]):
def __init__(
self,
get_infos: Callable[[], Iterator[IndexCandidateInfo]],
- installed: Optional[Candidate],
+ installed: Candidate | None,
prefers_installed: bool,
- incompatible_ids: Set[int],
+ incompatible_ids: set[int],
):
self._get_infos = get_infos
self._installed = installed
self._prefers_installed = prefers_installed
self._incompatible_ids = incompatible_ids
- self._bool: Optional[bool] = None
+ self._bool: bool | None = None
def __getitem__(self, index: Any) -> Any:
# Implemented to satisfy the ABC check. This is not needed by the
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/provider.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/provider.py
index ba4f03b34ee..40d611545a3 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/provider.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/provider.py
@@ -1,16 +1,11 @@
+from __future__ import annotations
+
import math
-from functools import lru_cache
+from collections.abc import Iterable, Iterator, Mapping, Sequence
+from functools import cache
from typing import (
TYPE_CHECKING,
- Dict,
- Iterable,
- Iterator,
- Mapping,
- Optional,
- Sequence,
- Tuple,
TypeVar,
- Union,
)
from pip._vendor.resolvelib.providers import AbstractProvider
@@ -59,7 +54,7 @@ def _get_with_identifier(
mapping: Mapping[str, V],
identifier: str,
default: D,
-) -> Union[D, V]:
+) -> D | V:
"""Get item from a package name lookup mapping with a resolver identifier.
This extra logic is needed when the target mapping is keyed by package
@@ -94,10 +89,10 @@ class PipProvider(_ProviderBase):
def __init__(
self,
factory: Factory,
- constraints: Dict[str, Constraint],
+ constraints: dict[str, Constraint],
ignore_dependencies: bool,
upgrade_strategy: str,
- user_requested: Dict[str, int],
+ user_requested: dict[str, int],
) -> None:
self._factory = factory
self._constraints = constraints
@@ -105,7 +100,7 @@ class PipProvider(_ProviderBase):
self._upgrade_strategy = upgrade_strategy
self._user_requested = user_requested
- def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
+ def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
return requirement_or_candidate.name
def narrow_requirement_selection(
@@ -113,8 +108,8 @@ class PipProvider(_ProviderBase):
identifiers: Iterable[str],
resolutions: Mapping[str, Candidate],
candidates: Mapping[str, Iterator[Candidate]],
- information: Mapping[str, Iterator["PreferenceInformation"]],
- backtrack_causes: Sequence["PreferenceInformation"],
+ information: Mapping[str, Iterator[PreferenceInformation]],
+ backtrack_causes: Sequence[PreferenceInformation],
) -> Iterable[str]:
"""Produce a subset of identifiers that should be considered before others.
@@ -156,9 +151,9 @@ class PipProvider(_ProviderBase):
identifier: str,
resolutions: Mapping[str, Candidate],
candidates: Mapping[str, Iterator[Candidate]],
- information: Mapping[str, Iterable["PreferenceInformation"]],
- backtrack_causes: Sequence["PreferenceInformation"],
- ) -> "Preference":
+ information: Mapping[str, Iterable[PreferenceInformation]],
+ backtrack_causes: Sequence[PreferenceInformation],
+ ) -> Preference:
"""Produce a sort key for given requirement based on preference.
The lower the return value is, the more preferred this group of
@@ -192,7 +187,7 @@ class PipProvider(_ProviderBase):
if not has_information:
direct = False
- ireqs: Tuple[Optional[InstallRequirement], ...] = ()
+ ireqs: tuple[InstallRequirement | None, ...] = ()
else:
# Go through the information and for each requirement,
# check if it's explicit (e.g., a direct link) and get the
@@ -271,7 +266,7 @@ class PipProvider(_ProviderBase):
)
@staticmethod
- @lru_cache(maxsize=None)
+ @cache
def is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
return requirement.is_satisfied_by(candidate)
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/reporter.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/reporter.py
index f8ad815fe9f..e694132ba5e 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/reporter.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/reporter.py
@@ -1,6 +1,8 @@
+from __future__ import annotations
+
from collections import defaultdict
from logging import getLogger
-from typing import Any, DefaultDict, Optional
+from typing import Any
from pip._vendor.resolvelib.reporters import BaseReporter
@@ -11,7 +13,7 @@ logger = getLogger(__name__)
class PipReporter(BaseReporter[Requirement, Candidate, str]):
def __init__(self) -> None:
- self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)
+ self.reject_count_by_package: defaultdict[str, int] = defaultdict(int)
self._messages_at_reject_count = {
1: (
@@ -72,7 +74,7 @@ class PipDebuggingReporter(BaseReporter[Requirement, Candidate, str]):
logger.info("Reporter.ending(%r)", state)
def adding_requirement(
- self, requirement: Requirement, parent: Optional[Candidate]
+ self, requirement: Requirement, parent: Candidate | None
) -> None:
logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/requirements.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/requirements.py
index b04f41b2191..447e36b5ac1 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/requirements.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/requirements.py
@@ -1,4 +1,6 @@
-from typing import Any, Optional
+from __future__ import annotations
+
+from typing import Any
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
@@ -51,8 +53,8 @@ class SpecifierRequirement(Requirement):
def __init__(self, ireq: InstallRequirement) -> None:
assert ireq.link is None, "This is a link, not a specifier"
self._ireq = ireq
- self._equal_cache: Optional[str] = None
- self._hash: Optional[int] = None
+ self._equal_cache: str | None = None
+ self._hash: int | None = None
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
@property
@@ -128,8 +130,8 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
def __init__(self, ireq: InstallRequirement) -> None:
assert ireq.link is None, "This is a link, not a specifier"
self._ireq = install_req_drop_extras(ireq)
- self._equal_cache: Optional[str] = None
- self._hash: Optional[int] = None
+ self._equal_cache: str | None = None
+ self._hash: int | None = None
self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
@property
@@ -159,7 +161,7 @@ class RequiresPythonRequirement(Requirement):
def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
self.specifier = specifier
self._specifier_string = str(specifier) # for faster __eq__
- self._hash: Optional[int] = None
+ self._hash: int | None = None
self._candidate = match
def __str__(self) -> str:
diff --git a/contrib/python/pip/pip/_internal/resolution/resolvelib/resolver.py b/contrib/python/pip/pip/_internal/resolution/resolvelib/resolver.py
index 24c9b16996a..1ba70c2b39e 100644
--- a/contrib/python/pip/pip/_internal/resolution/resolvelib/resolver.py
+++ b/contrib/python/pip/pip/_internal/resolution/resolvelib/resolver.py
@@ -1,8 +1,10 @@
+from __future__ import annotations
+
import contextlib
import functools
import logging
import os
-from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
+from typing import TYPE_CHECKING, cast
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible, ResolutionTooDeep
@@ -43,7 +45,7 @@ class Resolver(BaseResolver):
self,
preparer: RequirementPreparer,
finder: PackageFinder,
- wheel_cache: Optional[WheelCache],
+ wheel_cache: WheelCache | None,
make_install_req: InstallRequirementProvider,
use_user_site: bool,
ignore_dependencies: bool,
@@ -51,7 +53,7 @@ class Resolver(BaseResolver):
ignore_requires_python: bool,
force_reinstall: bool,
upgrade_strategy: str,
- py_version_info: Optional[Tuple[int, ...]] = None,
+ py_version_info: tuple[int, ...] | None = None,
):
super().__init__()
assert upgrade_strategy in self._allowed_strategies
@@ -69,10 +71,10 @@ class Resolver(BaseResolver):
)
self.ignore_dependencies = ignore_dependencies
self.upgrade_strategy = upgrade_strategy
- self._result: Optional[Result] = None
+ self._result: Result | None = None
def resolve(
- self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
+ self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
) -> RequirementSet:
collected = self.factory.collect_root_requirements(root_reqs)
provider = PipProvider(
@@ -187,7 +189,7 @@ class Resolver(BaseResolver):
def get_installation_order(
self, req_set: RequirementSet
- ) -> List[InstallRequirement]:
+ ) -> list[InstallRequirement]:
"""Get order for installation of requirements in RequirementSet.
The returned list contains a requirement before another that depends on
@@ -218,8 +220,8 @@ class Resolver(BaseResolver):
def get_topological_weights(
- graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
-) -> Dict[Optional[str], int]:
+ graph: DirectedGraph[str | None], requirement_keys: set[str]
+) -> dict[str | None, int]:
"""Assign weights to each node based on how "deep" they are.
This implementation may change at any point in the future without prior
@@ -245,14 +247,25 @@ def get_topological_weights(
We are only interested in the weights of packages that are in the
requirement_keys.
"""
- path: Set[Optional[str]] = set()
- weights: Dict[Optional[str], int] = {}
+ path: set[str | None] = set()
+ weights: dict[str | None, list[int]] = {}
- def visit(node: Optional[str]) -> None:
+ def visit(node: str | None) -> None:
if node in path:
# We hit a cycle, so we'll break it here.
return
+ # The walk is exponential and for pathologically connected graphs (which
+ # are the ones most likely to contain cycles in the first place) it can
+ # take until the heat-death of the universe. To counter this we limit
+ # the number of attempts to visit (i.e. traverse through) any given
+ # node. We choose a value here which gives decent enough coverage for
+ # fairly well behaved graphs, and still limits the walk complexity to be
+ # linear in nature.
+ cur_weights = weights.get(node, [])
+ if len(cur_weights) >= 5:
+ return
+
# Time to visit the children!
path.add(node)
for child in graph.iter_children(node):
@@ -262,14 +275,14 @@ def get_topological_weights(
if node not in requirement_keys:
return
- last_known_parent_count = weights.get(node, 0)
- weights[node] = max(last_known_parent_count, len(path))
+ cur_weights.append(len(path))
+ weights[node] = cur_weights
- # Simplify the graph, pruning leaves that have no dependencies.
- # This is needed for large graphs (say over 200 packages) because the
- # `visit` function is exponentially slower then, taking minutes.
+ # Simplify the graph, pruning leaves that have no dependencies. This is
+ # needed for large graphs (say over 200 packages) because the `visit`
+ # function is slower for large/densely connected graphs, taking minutes.
# See https://github.com/pypa/pip/issues/10557
- # We will loop until we explicitly break the loop.
+ # We repeat the pruning step until we have no more leaves to remove.
while True:
leaves = set()
for key in graph:
@@ -289,12 +302,13 @@ def get_topological_weights(
for leaf in leaves:
if leaf not in requirement_keys:
continue
- weights[leaf] = weight
+ weights[leaf] = [weight]
# Remove the leaves from the graph, making it simpler.
for leaf in leaves:
graph.remove(leaf)
- # Visit the remaining graph.
+ # Visit the remaining graph, this will only have nodes to handle if the
+ # graph had a cycle in it, which the pruning step above could not handle.
# `None` is guaranteed to be the root node by resolvelib.
visit(None)
@@ -303,13 +317,15 @@ def get_topological_weights(
difference = set(weights.keys()).difference(requirement_keys)
assert not difference, difference
- return weights
+ # Now give back all the weights, choosing the largest ones from what we
+ # accumulated.
+ return {node: max(wgts) for (node, wgts) in weights.items()}
def _req_set_item_sorter(
- item: Tuple[str, InstallRequirement],
- weights: Dict[Optional[str], int],
-) -> Tuple[int, str]:
+ item: tuple[str, InstallRequirement],
+ weights: dict[str | None, int],
+) -> tuple[int, str]:
"""Key function used to sort install requirements for installation.
Based on the "weight" mapping calculated in ``get_installation_order()``.
diff --git a/contrib/python/pip/pip/_internal/self_outdated_check.py b/contrib/python/pip/pip/_internal/self_outdated_check.py
index 2e0e3df3542..ca507f113a4 100644
--- a/contrib/python/pip/pip/_internal/self_outdated_check.py
+++ b/contrib/python/pip/pip/_internal/self_outdated_check.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import datetime
import functools
import hashlib
@@ -7,7 +9,7 @@ import optparse
import os.path
import sys
from dataclasses import dataclass
-from typing import Any, Callable, Dict, Optional
+from typing import Any, Callable
from pip._vendor.packaging.version import Version
from pip._vendor.packaging.version import parse as parse_version
@@ -54,7 +56,7 @@ def _convert_date(isodate: str) -> datetime.datetime:
class SelfCheckState:
def __init__(self, cache_dir: str) -> None:
- self._state: Dict[str, Any] = {}
+ self._state: dict[str, Any] = {}
self._statefile_path = None
# Try to load the existing state
@@ -74,7 +76,7 @@ class SelfCheckState:
def key(self) -> str:
return sys.prefix
- def get(self, current_time: datetime.datetime) -> Optional[str]:
+ def get(self, current_time: datetime.datetime) -> str | None:
"""Check if we have a not-outdated version loaded already."""
if not self._state:
return None
@@ -165,7 +167,7 @@ def was_installed_by_pip(pkg: str) -> bool:
def _get_current_remote_pip_version(
session: PipSession, options: optparse.Values
-) -> Optional[str]:
+) -> str | None:
# Lets use PackageFinder to see what the latest pip version is
link_collector = LinkCollector.create(
session,
@@ -196,8 +198,8 @@ def _self_version_check_logic(
state: SelfCheckState,
current_time: datetime.datetime,
local_version: Version,
- get_remote_version: Callable[[], Optional[str]],
-) -> Optional[UpgradePrompt]:
+ get_remote_version: Callable[[], str | None],
+) -> UpgradePrompt | None:
remote_version_str = state.get(current_time)
if remote_version_str is None:
remote_version_str = get_remote_version()
diff --git a/contrib/python/pip/pip/_internal/utils/appdirs.py b/contrib/python/pip/pip/_internal/utils/appdirs.py
index 42b6e548bb2..4152528f68c 100644
--- a/contrib/python/pip/pip/_internal/utils/appdirs.py
+++ b/contrib/python/pip/pip/_internal/utils/appdirs.py
@@ -8,7 +8,6 @@ and eventually drop this after all usages are changed.
import os
import sys
-from typing import List
from pip._vendor import platformdirs as _appdirs
@@ -40,7 +39,7 @@ def user_config_dir(appname: str, roaming: bool = True) -> str:
# for the discussion regarding site_config_dir locations
# see <https://github.com/pypa/pip/issues/1733>
-def site_config_dirs(appname: str) -> List[str]:
+def site_config_dirs(appname: str) -> list[str]:
if sys.platform == "darwin":
dirval = _appdirs.site_data_dir(appname, appauthor=False, multipath=True)
return dirval.split(os.pathsep)
diff --git a/contrib/python/pip/pip/_internal/utils/compat.py b/contrib/python/pip/pip/_internal/utils/compat.py
index d8b54e4ee51..324789f1d2e 100644
--- a/contrib/python/pip/pip/_internal/utils/compat.py
+++ b/contrib/python/pip/pip/_internal/utils/compat.py
@@ -7,7 +7,7 @@ import os
import sys
from typing import IO
-__all__ = ["get_path_uid", "stdlib_pkgs", "WINDOWS"]
+__all__ = ["get_path_uid", "stdlib_pkgs", "tomllib", "WINDOWS"]
logger = logging.getLogger(__name__)
@@ -67,6 +67,12 @@ else:
)
+if sys.version_info >= (3, 11):
+ import tomllib
+else:
+ from pip._vendor import tomli as tomllib
+
+
# packages in the stdlib that may have installation metadata, but should not be
# considered 'installed'. this theoretically could be determined based on
# dist.location (py27:`sysconfig.get_paths()['stdlib']`,
diff --git a/contrib/python/pip/pip/_internal/utils/compatibility_tags.py b/contrib/python/pip/pip/_internal/utils/compatibility_tags.py
index edbc7c37a7d..6d98171daf9 100644
--- a/contrib/python/pip/pip/_internal/utils/compatibility_tags.py
+++ b/contrib/python/pip/pip/_internal/utils/compatibility_tags.py
@@ -1,7 +1,8 @@
"""Generate and work with PEP 425 Compatibility Tags."""
+from __future__ import annotations
+
import re
-from typing import List, Optional, Tuple
from pip._vendor.packaging.tags import (
PythonVersion,
@@ -19,12 +20,12 @@ from pip._vendor.packaging.tags import (
_apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
-def version_info_to_nodot(version_info: Tuple[int, ...]) -> str:
+def version_info_to_nodot(version_info: tuple[int, ...]) -> str:
# Only use up to the first two numbers.
return "".join(map(str, version_info[:2]))
-def _mac_platforms(arch: str) -> List[str]:
+def _mac_platforms(arch: str) -> list[str]:
match = _apple_arch_pat.match(arch)
if match:
name, major, minor, actual_arch = match.groups()
@@ -44,7 +45,7 @@ def _mac_platforms(arch: str) -> List[str]:
return arches
-def _ios_platforms(arch: str) -> List[str]:
+def _ios_platforms(arch: str) -> list[str]:
match = _apple_arch_pat.match(arch)
if match:
name, major, minor, actual_multiarch = match.groups()
@@ -64,7 +65,7 @@ def _ios_platforms(arch: str) -> List[str]:
return arches
-def _android_platforms(arch: str) -> List[str]:
+def _android_platforms(arch: str) -> list[str]:
match = re.fullmatch(r"android_(\d+)_(.+)", arch)
if match:
api_level, abi = match.groups()
@@ -74,7 +75,7 @@ def _android_platforms(arch: str) -> List[str]:
return [arch]
-def _custom_manylinux_platforms(arch: str) -> List[str]:
+def _custom_manylinux_platforms(arch: str) -> list[str]:
arches = [arch]
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
if arch_prefix == "manylinux2014":
@@ -95,7 +96,7 @@ def _custom_manylinux_platforms(arch: str) -> List[str]:
return arches
-def _get_custom_platforms(arch: str) -> List[str]:
+def _get_custom_platforms(arch: str) -> list[str]:
arch_prefix, arch_sep, arch_suffix = arch.partition("_")
if arch.startswith("macosx"):
arches = _mac_platforms(arch)
@@ -110,7 +111,7 @@ def _get_custom_platforms(arch: str) -> List[str]:
return arches
-def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[str]]:
+def _expand_allowed_platforms(platforms: list[str] | None) -> list[str] | None:
if not platforms:
return None
@@ -135,7 +136,7 @@ def _get_python_version(version: str) -> PythonVersion:
def _get_custom_interpreter(
- implementation: Optional[str] = None, version: Optional[str] = None
+ implementation: str | None = None, version: str | None = None
) -> str:
if implementation is None:
implementation = interpreter_name()
@@ -145,11 +146,11 @@ def _get_custom_interpreter(
def get_supported(
- version: Optional[str] = None,
- platforms: Optional[List[str]] = None,
- impl: Optional[str] = None,
- abis: Optional[List[str]] = None,
-) -> List[Tag]:
+ version: str | None = None,
+ platforms: list[str] | None = None,
+ impl: str | None = None,
+ abis: list[str] | None = None,
+) -> list[Tag]:
"""Return a list of supported tags for each version specified in
`versions`.
@@ -162,9 +163,9 @@ def get_supported(
:param abis: specify a list of abis you want valid
tags for, or None. If None, use the local interpreter abi.
"""
- supported: List[Tag] = []
+ supported: list[Tag] = []
- python_version: Optional[PythonVersion] = None
+ python_version: PythonVersion | None = None
if version is not None:
python_version = _get_python_version(version)
diff --git a/contrib/python/pip/pip/_internal/utils/deprecation.py b/contrib/python/pip/pip/_internal/utils/deprecation.py
index 0911147e784..96e7783feb3 100644
--- a/contrib/python/pip/pip/_internal/utils/deprecation.py
+++ b/contrib/python/pip/pip/_internal/utils/deprecation.py
@@ -2,9 +2,11 @@
A module that implements tooling to enable easy warnings about deprecations.
"""
+from __future__ import annotations
+
import logging
import warnings
-from typing import Any, Optional, TextIO, Type, Union
+from typing import Any, TextIO
from pip._vendor.packaging.version import parse
@@ -22,12 +24,12 @@ _original_showwarning: Any = None
# Warnings <-> Logging Integration
def _showwarning(
- message: Union[Warning, str],
- category: Type[Warning],
+ message: Warning | str,
+ category: type[Warning],
filename: str,
lineno: int,
- file: Optional[TextIO] = None,
- line: Optional[str] = None,
+ file: TextIO | None = None,
+ line: str | None = None,
) -> None:
if file is not None:
if _original_showwarning is not None:
@@ -55,10 +57,10 @@ def install_warning_logger() -> None:
def deprecated(
*,
reason: str,
- replacement: Optional[str],
- gone_in: Optional[str],
- feature_flag: Optional[str] = None,
- issue: Optional[int] = None,
+ replacement: str | None,
+ gone_in: str | None,
+ feature_flag: str | None = None,
+ issue: int | None = None,
) -> None:
"""Helper to deprecate existing functionality.
diff --git a/contrib/python/pip/pip/_internal/utils/direct_url_helpers.py b/contrib/python/pip/pip/_internal/utils/direct_url_helpers.py
index 66020d3964a..3cbc1e76344 100644
--- a/contrib/python/pip/pip/_internal/utils/direct_url_helpers.py
+++ b/contrib/python/pip/pip/_internal/utils/direct_url_helpers.py
@@ -1,4 +1,4 @@
-from typing import Optional
+from __future__ import annotations
from pip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo
from pip._internal.models.link import Link
@@ -37,7 +37,7 @@ def direct_url_for_editable(source_dir: str) -> DirectUrl:
def direct_url_from_link(
- link: Link, source_dir: Optional[str] = None, link_is_in_wheel_cache: bool = False
+ link: Link, source_dir: str | None = None, link_is_in_wheel_cache: bool = False
) -> DirectUrl:
if link.is_vcs:
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
diff --git a/contrib/python/pip/pip/_internal/utils/egg_link.py b/contrib/python/pip/pip/_internal/utils/egg_link.py
index 4a384a63682..dc85a58b32c 100644
--- a/contrib/python/pip/pip/_internal/utils/egg_link.py
+++ b/contrib/python/pip/pip/_internal/utils/egg_link.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import os
import re
import sys
-from typing import List, Optional
from pip._internal.locations import site_packages, user_site
from pip._internal.utils.virtualenv import (
@@ -15,7 +16,7 @@ __all__ = [
]
-def _egg_link_names(raw_name: str) -> List[str]:
+def _egg_link_names(raw_name: str) -> list[str]:
"""
Convert a Name metadata value to a .egg-link name, by applying
the same substitution as pkg_resources's safe_name function.
@@ -30,7 +31,7 @@ def _egg_link_names(raw_name: str) -> List[str]:
]
-def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]:
+def egg_link_path_from_sys_path(raw_name: str) -> str | None:
"""
Look for a .egg-link file for project name, by walking sys.path.
"""
@@ -43,7 +44,7 @@ def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]:
return None
-def egg_link_path_from_location(raw_name: str) -> Optional[str]:
+def egg_link_path_from_location(raw_name: str) -> str | None:
"""
Return the path for the .egg-link file if it exists, otherwise, None.
@@ -61,7 +62,7 @@ def egg_link_path_from_location(raw_name: str) -> Optional[str]:
This method will just return the first one found.
"""
- sites: List[str] = []
+ sites: list[str] = []
if running_under_virtualenv():
sites.append(site_packages)
if not virtualenv_no_global() and user_site:
diff --git a/contrib/python/pip/pip/_internal/utils/entrypoints.py b/contrib/python/pip/pip/_internal/utils/entrypoints.py
index 696148c5097..e3a150eeba0 100644
--- a/contrib/python/pip/pip/_internal/utils/entrypoints.py
+++ b/contrib/python/pip/pip/_internal/utils/entrypoints.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import itertools
import os
import shutil
import sys
-from typing import List, Optional
from pip._internal.cli.main import main
from pip._internal.utils.compat import WINDOWS
@@ -20,7 +21,7 @@ if WINDOWS:
]
-def _wrapper(args: Optional[List[str]] = None) -> int:
+def _wrapper(args: list[str] | None = None) -> int:
"""Central wrapper for all old entrypoints.
Historically pip has had several entrypoints defined. Because of issues
diff --git a/contrib/python/pip/pip/_internal/utils/filesystem.py b/contrib/python/pip/pip/_internal/utils/filesystem.py
index 22e356cdd75..d7c05243876 100644
--- a/contrib/python/pip/pip/_internal/utils/filesystem.py
+++ b/contrib/python/pip/pip/_internal/utils/filesystem.py
@@ -1,11 +1,14 @@
+from __future__ import annotations
+
import fnmatch
import os
import os.path
import random
import sys
+from collections.abc import Generator
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
-from typing import Any, BinaryIO, Generator, List, Union, cast
+from typing import Any, BinaryIO, cast
from pip._internal.utils.compat import get_path_uid
from pip._internal.utils.misc import format_size
@@ -115,17 +118,17 @@ def _test_writable_dir_win(path: str) -> bool:
raise OSError("Unexpected condition testing for writable directory")
-def find_files(path: str, pattern: str) -> List[str]:
+def find_files(path: str, pattern: str) -> list[str]:
"""Returns a list of absolute paths of files beneath path, recursively,
with filenames which match the UNIX-style shell glob pattern."""
- result: List[str] = []
+ result: list[str] = []
for root, _, files in os.walk(path):
matches = fnmatch.filter(files, pattern)
result.extend(os.path.join(root, f) for f in matches)
return result
-def file_size(path: str) -> Union[int, float]:
+def file_size(path: str) -> int | float:
# If it's a symlink, return 0.
if os.path.islink(path):
return 0
@@ -136,7 +139,7 @@ def format_file_size(path: str) -> str:
return format_size(file_size(path))
-def directory_size(path: str) -> Union[int, float]:
+def directory_size(path: str) -> int | float:
size = 0.0
for root, _dirs, files in os.walk(path):
for filename in files:
diff --git a/contrib/python/pip/pip/_internal/utils/filetypes.py b/contrib/python/pip/pip/_internal/utils/filetypes.py
index 5644638222c..2b8baad7cd6 100644
--- a/contrib/python/pip/pip/_internal/utils/filetypes.py
+++ b/contrib/python/pip/pip/_internal/utils/filetypes.py
@@ -1,20 +1,18 @@
"""Filetype information."""
-from typing import Tuple
-
from pip._internal.utils.misc import splitext
WHEEL_EXTENSION = ".whl"
-BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz")
-XZ_EXTENSIONS: Tuple[str, ...] = (
+BZ2_EXTENSIONS: tuple[str, ...] = (".tar.bz2", ".tbz")
+XZ_EXTENSIONS: tuple[str, ...] = (
".tar.xz",
".txz",
".tlz",
".tar.lz",
".tar.lzma",
)
-ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION)
-TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
+ZIP_EXTENSIONS: tuple[str, ...] = (".zip", WHEEL_EXTENSION)
+TAR_EXTENSIONS: tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
diff --git a/contrib/python/pip/pip/_internal/utils/glibc.py b/contrib/python/pip/pip/_internal/utils/glibc.py
index 998868ff2a4..2cb3013c7d9 100644
--- a/contrib/python/pip/pip/_internal/utils/glibc.py
+++ b/contrib/python/pip/pip/_internal/utils/glibc.py
@@ -1,14 +1,15 @@
+from __future__ import annotations
+
import os
import sys
-from typing import Optional, Tuple
-def glibc_version_string() -> Optional[str]:
+def glibc_version_string() -> str | None:
"Returns glibc version string, or None if not using glibc."
return glibc_version_string_confstr() or glibc_version_string_ctypes()
-def glibc_version_string_confstr() -> Optional[str]:
+def glibc_version_string_confstr() -> str | None:
"Primary implementation of glibc_version_string using os.confstr."
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
# to be broken or missing. This strategy is used in the standard library
@@ -28,7 +29,7 @@ def glibc_version_string_confstr() -> Optional[str]:
return version
-def glibc_version_string_ctypes() -> Optional[str]:
+def glibc_version_string_ctypes() -> str | None:
"Fallback implementation of glibc_version_string using ctypes."
try:
@@ -88,7 +89,7 @@ def glibc_version_string_ctypes() -> Optional[str]:
# versions that was generated by pip 8.1.2 and earlier is useless and
# misleading. Solution: instead of using platform, use our code that actually
# works.
-def libc_ver() -> Tuple[str, str]:
+def libc_ver() -> tuple[str, str]:
"""Try to determine the glibc version
Returns a tuple of strings (lib, version) which default to empty strings
diff --git a/contrib/python/pip/pip/_internal/utils/hashes.py b/contrib/python/pip/pip/_internal/utils/hashes.py
index 535e94fca0c..3d8c125ada3 100644
--- a/contrib/python/pip/pip/_internal/utils/hashes.py
+++ b/contrib/python/pip/pip/_internal/utils/hashes.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
import hashlib
-from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, NoReturn, Optional
+from collections.abc import Iterable
+from typing import TYPE_CHECKING, BinaryIO, NoReturn
from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
from pip._internal.utils.misc import read_chunks
@@ -24,7 +27,7 @@ class Hashes:
"""
- def __init__(self, hashes: Optional[Dict[str, List[str]]] = None) -> None:
+ def __init__(self, hashes: dict[str, list[str]] | None = None) -> None:
"""
:param hashes: A dict of algorithm names pointing to lists of allowed
hex digests
@@ -36,7 +39,7 @@ class Hashes:
allowed[alg] = [k.lower() for k in sorted(keys)]
self._allowed = allowed
- def __and__(self, other: "Hashes") -> "Hashes":
+ def __and__(self, other: Hashes) -> Hashes:
if not isinstance(other, Hashes):
return NotImplemented
@@ -86,7 +89,7 @@ class Hashes:
return
self._raise(gots)
- def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
+ def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
raise HashMismatch(self._allowed, gots)
def check_against_file(self, file: BinaryIO) -> None:
@@ -101,7 +104,7 @@ class Hashes:
with open(path, "rb") as file:
return self.check_against_file(file)
- def has_one_of(self, hashes: Dict[str, str]) -> bool:
+ def has_one_of(self, hashes: dict[str, str]) -> bool:
"""Return whether any of the given hashes are allowed."""
for hash_name, hex_digest in hashes.items():
if self.is_hash_allowed(hash_name, hex_digest):
@@ -143,5 +146,5 @@ class MissingHashes(Hashes):
# empty list, it will never match, so an error will always raise.
super().__init__(hashes={FAVORITE_HASH: []})
- def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
+ def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
raise HashMissing(gots[FAVORITE_HASH].hexdigest())
diff --git a/contrib/python/pip/pip/_internal/utils/logging.py b/contrib/python/pip/pip/_internal/utils/logging.py
index 099a92c496d..5cdbeb7f753 100644
--- a/contrib/python/pip/pip/_internal/utils/logging.py
+++ b/contrib/python/pip/pip/_internal/utils/logging.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import errno
import logging
@@ -5,10 +7,11 @@ import logging.handlers
import os
import sys
import threading
+from collections.abc import Generator
from dataclasses import dataclass
from io import TextIOWrapper
from logging import Filter
-from typing import Any, ClassVar, Generator, List, Optional, Type
+from typing import Any, ClassVar
from pip._vendor.rich.console import (
Console,
@@ -40,7 +43,7 @@ class BrokenStdoutLoggingError(Exception):
"""
-def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:
+def _is_broken_pipe_error(exc_class: type[BaseException], exc: BaseException) -> bool:
if exc_class is BrokenPipeError:
return True
@@ -156,7 +159,7 @@ def get_console(*, stderr: bool = False) -> Console:
class RichPipStreamHandler(RichHandler):
- KEYWORDS: ClassVar[Optional[List[str]]] = []
+ KEYWORDS: ClassVar[list[str] | None] = []
def __init__(self, console: Console) -> None:
super().__init__(
@@ -169,7 +172,7 @@ class RichPipStreamHandler(RichHandler):
# Our custom override on Rich's logger, to make things work as we need them to.
def emit(self, record: logging.LogRecord) -> None:
- style: Optional[Style] = None
+ style: Style | None = None
# If we are given a diagnostic error to present, present it with indentation.
if getattr(record, "rich", False):
@@ -240,7 +243,7 @@ class ExcludeLoggerFilter(Filter):
return not super().filter(record)
-def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:
+def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) -> int:
"""Configures and sets up all of the logging
Returns the requested logging level, as its integer value.
diff --git a/contrib/python/pip/pip/_internal/utils/misc.py b/contrib/python/pip/pip/_internal/utils/misc.py
index 44f6a05fbdd..3a28e8449de 100644
--- a/contrib/python/pip/pip/_internal/utils/misc.py
+++ b/contrib/python/pip/pip/_internal/utils/misc.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import errno
import getpass
import hashlib
@@ -9,6 +11,7 @@ import stat
import sys
import sysconfig
import urllib.parse
+from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
from dataclasses import dataclass
from functools import partial
from io import StringIO
@@ -19,18 +22,9 @@ from typing import (
Any,
BinaryIO,
Callable,
- Generator,
- Iterable,
- Iterator,
- List,
- Mapping,
Optional,
- Sequence,
TextIO,
- Tuple,
- Type,
TypeVar,
- Union,
cast,
)
@@ -64,9 +58,9 @@ __all__ = [
logger = logging.getLogger(__name__)
T = TypeVar("T")
-ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
-VersionInfo = Tuple[int, int, int]
-NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
+ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
+VersionInfo = tuple[int, int, int]
+NetlocTuple = tuple[str, tuple[Optional[str], Optional[str]]]
OnExc = Callable[[FunctionType, Path, BaseException], Any]
OnErr = Callable[[FunctionType, Path, ExcInfo], Any]
@@ -80,7 +74,7 @@ def get_pip_version() -> str:
return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})"
-def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]:
+def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]:
"""
Convert a tuple of ints representing a Python version to one of length
three.
@@ -123,9 +117,7 @@ def get_prog() -> str:
# Retry every half second for up to 3 seconds
@retry(stop_after_delay=3, wait=0.5)
-def rmtree(
- dir: str, ignore_errors: bool = False, onexc: Optional[OnExc] = None
-) -> None:
+def rmtree(dir: str, ignore_errors: bool = False, onexc: OnExc | None = None) -> None:
if ignore_errors:
onexc = _onerror_ignore
if onexc is None:
@@ -149,7 +141,7 @@ def _onerror_reraise(*_args: Any) -> None:
def rmtree_errorhandler(
func: FunctionType,
path: Path,
- exc_info: Union[ExcInfo, BaseException],
+ exc_info: ExcInfo | BaseException,
*,
onexc: OnExc = _onerror_reraise,
) -> None:
@@ -276,7 +268,7 @@ def format_size(bytes: float) -> str:
return f"{int(bytes)} bytes"
-def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
+def tabulate(rows: Iterable[Iterable[Any]]) -> tuple[list[str], list[int]]:
"""Return a list of formatted rows and a list of column sizes.
For example::
@@ -331,7 +323,7 @@ def normalize_path(path: str, resolve_symlinks: bool = True) -> str:
return os.path.normcase(path)
-def splitext(path: str) -> Tuple[str, str]:
+def splitext(path: str) -> tuple[str, str]:
"""Like os.path.splitext, but take off .tar too"""
base, ext = posixpath.splitext(path)
if base.lower().endswith(".tar"):
@@ -379,7 +371,7 @@ class StreamWrapper(StringIO):
orig_stream: TextIO
@classmethod
- def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper":
+ def from_stream(cls, orig_stream: TextIO) -> StreamWrapper:
ret = cls()
ret.orig_stream = orig_stream
return ret
@@ -392,14 +384,14 @@ class StreamWrapper(StringIO):
# Simulates an enum
-def enum(*sequential: Any, **named: Any) -> Type[Any]:
+def enum(*sequential: Any, **named: Any) -> type[Any]:
enums = dict(zip(sequential, range(len(sequential))), **named)
reverse = {value: key for key, value in enums.items()}
enums["reverse_mapping"] = reverse
return type("Enum", (), enums)
-def build_netloc(host: str, port: Optional[int]) -> str:
+def build_netloc(host: str, port: int | None) -> str:
"""
Build a netloc from a host-port pair
"""
@@ -421,7 +413,7 @@ def build_url_from_netloc(netloc: str, scheme: str = "https") -> str:
return f"{scheme}://{netloc}"
-def parse_netloc(netloc: str) -> Tuple[Optional[str], Optional[int]]:
+def parse_netloc(netloc: str) -> tuple[str | None, int | None]:
"""
Return the host-port pair from a netloc.
"""
@@ -443,7 +435,7 @@ def split_auth_from_netloc(netloc: str) -> NetlocTuple:
# behaves if more than one @ is present (which can be checked using
# the password attribute of urlsplit()'s return value).
auth, netloc = netloc.rsplit("@", 1)
- pw: Optional[str] = None
+ pw: str | None = None
if ":" in auth:
# Split from the left because that's how urllib.parse.urlsplit()
# behaves if more than one : is present (which again can be checked
@@ -480,8 +472,8 @@ def redact_netloc(netloc: str) -> str:
def _transform_url(
- url: str, transform_netloc: Callable[[str], Tuple[Any, ...]]
-) -> Tuple[str, NetlocTuple]:
+ url: str, transform_netloc: Callable[[str], tuple[Any, ...]]
+) -> tuple[str, NetlocTuple]:
"""Transform and replace netloc in a url.
transform_netloc is a function taking the netloc and returning a
@@ -503,13 +495,13 @@ def _get_netloc(netloc: str) -> NetlocTuple:
return split_auth_from_netloc(netloc)
-def _redact_netloc(netloc: str) -> Tuple[str]:
+def _redact_netloc(netloc: str) -> tuple[str]:
return (redact_netloc(netloc),)
def split_auth_netloc_from_url(
url: str,
-) -> Tuple[str, str, Tuple[Optional[str], Optional[str]]]:
+) -> tuple[str, str, tuple[str | None, str | None]]:
"""
Parse a url into separate netloc, auth, and url with no auth.
@@ -614,7 +606,7 @@ def is_console_interactive() -> bool:
return sys.stdin is not None and sys.stdin.isatty()
-def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
+def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]:
"""Return (hash, length) for path using hashlib.sha256()"""
h = hashlib.sha256()
@@ -626,7 +618,7 @@ def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
return h, length
-def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
+def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]:
"""
Return paired elements.
@@ -639,7 +631,7 @@ def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
def partition(
pred: Callable[[T], bool], iterable: Iterable[T]
-) -> Tuple[Iterable[T], Iterable[T]]:
+) -> tuple[Iterable[T], Iterable[T]]:
"""
Use a predicate to partition entries into false entries and true entries,
like
@@ -656,9 +648,9 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
config_holder: Any,
source_dir: str,
build_backend: str,
- backend_path: Optional[str] = None,
- runner: Optional[Callable[..., None]] = None,
- python_executable: Optional[str] = None,
+ backend_path: str | None = None,
+ runner: Callable[..., None] | None = None,
+ python_executable: str | None = None,
):
super().__init__(
source_dir, build_backend, backend_path, runner, python_executable
@@ -668,8 +660,8 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def build_wheel(
self,
wheel_directory: str,
- config_settings: Optional[Mapping[str, Any]] = None,
- metadata_directory: Optional[str] = None,
+ config_settings: Mapping[str, Any] | None = None,
+ metadata_directory: str | None = None,
) -> str:
cs = self.config_holder.config_settings
return super().build_wheel(
@@ -679,7 +671,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def build_sdist(
self,
sdist_directory: str,
- config_settings: Optional[Mapping[str, Any]] = None,
+ config_settings: Mapping[str, Any] | None = None,
) -> str:
cs = self.config_holder.config_settings
return super().build_sdist(sdist_directory, config_settings=cs)
@@ -687,8 +679,8 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def build_editable(
self,
wheel_directory: str,
- config_settings: Optional[Mapping[str, Any]] = None,
- metadata_directory: Optional[str] = None,
+ config_settings: Mapping[str, Any] | None = None,
+ metadata_directory: str | None = None,
) -> str:
cs = self.config_holder.config_settings
return super().build_editable(
@@ -696,19 +688,19 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
)
def get_requires_for_build_wheel(
- self, config_settings: Optional[Mapping[str, Any]] = None
+ self, config_settings: Mapping[str, Any] | None = None
) -> Sequence[str]:
cs = self.config_holder.config_settings
return super().get_requires_for_build_wheel(config_settings=cs)
def get_requires_for_build_sdist(
- self, config_settings: Optional[Mapping[str, Any]] = None
+ self, config_settings: Mapping[str, Any] | None = None
) -> Sequence[str]:
cs = self.config_holder.config_settings
return super().get_requires_for_build_sdist(config_settings=cs)
def get_requires_for_build_editable(
- self, config_settings: Optional[Mapping[str, Any]] = None
+ self, config_settings: Mapping[str, Any] | None = None
) -> Sequence[str]:
cs = self.config_holder.config_settings
return super().get_requires_for_build_editable(config_settings=cs)
@@ -716,7 +708,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def prepare_metadata_for_build_wheel(
self,
metadata_directory: str,
- config_settings: Optional[Mapping[str, Any]] = None,
+ config_settings: Mapping[str, Any] | None = None,
_allow_fallback: bool = True,
) -> str:
cs = self.config_holder.config_settings
@@ -729,9 +721,9 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
def prepare_metadata_for_build_editable(
self,
metadata_directory: str,
- config_settings: Optional[Mapping[str, Any]] = None,
+ config_settings: Mapping[str, Any] | None = None,
_allow_fallback: bool = True,
- ) -> Optional[str]:
+ ) -> str | None:
cs = self.config_holder.config_settings
return super().prepare_metadata_for_build_editable(
metadata_directory=metadata_directory,
diff --git a/contrib/python/pip/pip/_internal/utils/packaging.py b/contrib/python/pip/pip/_internal/utils/packaging.py
index 1295b7ffe20..3cbc0490aa1 100644
--- a/contrib/python/pip/pip/_internal/utils/packaging.py
+++ b/contrib/python/pip/pip/_internal/utils/packaging.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import functools
import logging
-from typing import Optional, Tuple
from pip._vendor.packaging import specifiers, version
from pip._vendor.packaging.requirements import Requirement
@@ -10,7 +11,7 @@ logger = logging.getLogger(__name__)
@functools.lru_cache(maxsize=32)
def check_requires_python(
- requires_python: Optional[str], version_info: Tuple[int, ...]
+ requires_python: str | None, version_info: tuple[int, ...]
) -> bool:
"""
Check if the given Python version matches a "Requires-Python" specifier.
diff --git a/contrib/python/pip/pip/_internal/utils/retry.py b/contrib/python/pip/pip/_internal/utils/retry.py
index abfe07286ea..27d3b6e7803 100644
--- a/contrib/python/pip/pip/_internal/utils/retry.py
+++ b/contrib/python/pip/pip/_internal/utils/retry.py
@@ -1,11 +1,14 @@
+from __future__ import annotations
+
import functools
from time import perf_counter, sleep
-from typing import Callable, TypeVar
+from typing import TYPE_CHECKING, Callable, TypeVar
-from pip._vendor.typing_extensions import ParamSpec
+if TYPE_CHECKING:
+ from typing_extensions import ParamSpec
-T = TypeVar("T")
-P = ParamSpec("P")
+ T = TypeVar("T")
+ P = ParamSpec("P")
def retry(
diff --git a/contrib/python/pip/pip/_internal/utils/setuptools_build.py b/contrib/python/pip/pip/_internal/utils/setuptools_build.py
index f178f4b3d99..1b8c7a1c21f 100644
--- a/contrib/python/pip/pip/_internal/utils/setuptools_build.py
+++ b/contrib/python/pip/pip/_internal/utils/setuptools_build.py
@@ -1,6 +1,8 @@
+from __future__ import annotations
+
import sys
import textwrap
-from typing import List, Optional, Sequence
+from collections.abc import Sequence
# Shim to wrap setup.py invocation with setuptools
# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on
@@ -49,10 +51,10 @@ _SETUPTOOLS_SHIM = textwrap.dedent(
def make_setuptools_shim_args(
setup_py_path: str,
- global_options: Optional[Sequence[str]] = None,
+ global_options: Sequence[str] | None = None,
no_user_config: bool = False,
unbuffered_output: bool = False,
-) -> List[str]:
+) -> list[str]:
"""
Get setuptools command arguments with shim wrapped setup file invocation.
@@ -78,7 +80,7 @@ def make_setuptools_bdist_wheel_args(
global_options: Sequence[str],
build_options: Sequence[str],
destination_dir: str,
-) -> List[str]:
+) -> list[str]:
# NOTE: Eventually, we'd want to also -S to the flags here, when we're
# isolating. Currently, it breaks Python in virtualenvs, because it
# relies on site.py to find parts of the standard library outside the
@@ -94,7 +96,7 @@ def make_setuptools_bdist_wheel_args(
def make_setuptools_clean_args(
setup_py_path: str,
global_options: Sequence[str],
-) -> List[str]:
+) -> list[str]:
args = make_setuptools_shim_args(
setup_py_path, global_options=global_options, unbuffered_output=True
)
@@ -107,10 +109,10 @@ def make_setuptools_develop_args(
*,
global_options: Sequence[str],
no_user_config: bool,
- prefix: Optional[str],
- home: Optional[str],
+ prefix: str | None,
+ home: str | None,
use_user_site: bool,
-) -> List[str]:
+) -> list[str]:
assert not (use_user_site and prefix)
args = make_setuptools_shim_args(
@@ -134,9 +136,9 @@ def make_setuptools_develop_args(
def make_setuptools_egg_info_args(
setup_py_path: str,
- egg_info_dir: Optional[str],
+ egg_info_dir: str | None,
no_user_config: bool,
-) -> List[str]:
+) -> list[str]:
args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config)
args += ["egg_info"]
diff --git a/contrib/python/pip/pip/_internal/utils/subprocess.py b/contrib/python/pip/pip/_internal/utils/subprocess.py
index cb2e23f007a..3e7b83f308c 100644
--- a/contrib/python/pip/pip/_internal/utils/subprocess.py
+++ b/contrib/python/pip/pip/_internal/utils/subprocess.py
@@ -1,8 +1,11 @@
+from __future__ import annotations
+
import logging
import os
import shlex
import subprocess
-from typing import Any, Callable, Iterable, List, Literal, Mapping, Optional, Union
+from collections.abc import Iterable, Mapping
+from typing import Any, Callable, Literal, Union
from pip._vendor.rich.markup import escape
@@ -11,10 +14,10 @@ from pip._internal.exceptions import InstallationSubprocessError
from pip._internal.utils.logging import VERBOSE, subprocess_logger
from pip._internal.utils.misc import HiddenText
-CommandArgs = List[Union[str, HiddenText]]
+CommandArgs = list[Union[str, HiddenText]]
-def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
+def make_command(*args: str | HiddenText | CommandArgs) -> CommandArgs:
"""
Create a CommandArgs object.
"""
@@ -31,7 +34,7 @@ def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
return command_args
-def format_command_args(args: Union[List[str], CommandArgs]) -> str:
+def format_command_args(args: list[str] | CommandArgs) -> str:
"""
Format command arguments for display.
"""
@@ -46,7 +49,7 @@ def format_command_args(args: Union[List[str], CommandArgs]) -> str:
)
-def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
+def reveal_command_args(args: list[str] | CommandArgs) -> list[str]:
"""
Return the arguments in their raw, unredacted form.
"""
@@ -54,16 +57,16 @@ def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
def call_subprocess(
- cmd: Union[List[str], CommandArgs],
+ cmd: list[str] | CommandArgs,
show_stdout: bool = False,
- cwd: Optional[str] = None,
- on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
- extra_ok_returncodes: Optional[Iterable[int]] = None,
- extra_environ: Optional[Mapping[str, Any]] = None,
- unset_environ: Optional[Iterable[str]] = None,
- spinner: Optional[SpinnerInterface] = None,
- log_failed_cmd: Optional[bool] = True,
- stdout_only: Optional[bool] = False,
+ cwd: str | None = None,
+ on_returncode: Literal["raise", "warn", "ignore"] = "raise",
+ extra_ok_returncodes: Iterable[int] | None = None,
+ extra_environ: Mapping[str, Any] | None = None,
+ unset_environ: Iterable[str] | None = None,
+ spinner: SpinnerInterface | None = None,
+ log_failed_cmd: bool | None = True,
+ stdout_only: bool | None = False,
*,
command_desc: str,
) -> str:
@@ -229,9 +232,9 @@ def runner_with_spinner_message(message: str) -> Callable[..., None]:
"""
def runner(
- cmd: List[str],
- cwd: Optional[str] = None,
- extra_environ: Optional[Mapping[str, Any]] = None,
+ cmd: list[str],
+ cwd: str | None = None,
+ extra_environ: Mapping[str, Any] | None = None,
) -> None:
with open_spinner(message) as spinner:
call_subprocess(
diff --git a/contrib/python/pip/pip/_internal/utils/temp_dir.py b/contrib/python/pip/pip/_internal/utils/temp_dir.py
index 06668e8ab2d..a9afa76c848 100644
--- a/contrib/python/pip/pip/_internal/utils/temp_dir.py
+++ b/contrib/python/pip/pip/_internal/utils/temp_dir.py
@@ -1,20 +1,18 @@
+from __future__ import annotations
+
import errno
import itertools
import logging
import os.path
import tempfile
import traceback
+from collections.abc import Generator
from contextlib import ExitStack, contextmanager
from pathlib import Path
from typing import (
Any,
Callable,
- Dict,
- Generator,
- List,
- Optional,
TypeVar,
- Union,
)
from pip._internal.utils.misc import enum, rmtree
@@ -33,7 +31,7 @@ tempdir_kinds = enum(
)
-_tempdir_manager: Optional[ExitStack] = None
+_tempdir_manager: ExitStack | None = None
@contextmanager
@@ -51,7 +49,7 @@ class TempDirectoryTypeRegistry:
"""Manages temp directory behavior"""
def __init__(self) -> None:
- self._should_delete: Dict[str, bool] = {}
+ self._should_delete: dict[str, bool] = {}
def set_delete(self, kind: str, value: bool) -> None:
"""Indicate whether a TempDirectory of the given kind should be
@@ -66,7 +64,7 @@ class TempDirectoryTypeRegistry:
return self._should_delete.get(kind, True)
-_tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
+_tempdir_registry: TempDirectoryTypeRegistry | None = None
@contextmanager
@@ -113,8 +111,8 @@ class TempDirectory:
def __init__(
self,
- path: Optional[str] = None,
- delete: Union[bool, None, _Default] = _default,
+ path: str | None = None,
+ delete: bool | None | _Default = _default,
kind: str = "temp",
globally_managed: bool = False,
ignore_cleanup_errors: bool = True,
@@ -184,7 +182,7 @@ class TempDirectory:
if not os.path.exists(self._path):
return
- errors: List[BaseException] = []
+ errors: list[BaseException] = []
def onerror(
func: Callable[..., Any],
@@ -245,7 +243,7 @@ class AdjacentTempDirectory(TempDirectory):
# with leading '-' and invalid metadata
LEADING_CHARS = "-~.=%0123456789"
- def __init__(self, original: str, delete: Optional[bool] = None) -> None:
+ def __init__(self, original: str, delete: bool | None = None) -> None:
self.original = original.rstrip("/\\")
super().__init__(delete=delete)
diff --git a/contrib/python/pip/pip/_internal/utils/unpacking.py b/contrib/python/pip/pip/_internal/utils/unpacking.py
index feb40f8289b..0ad3129acf4 100644
--- a/contrib/python/pip/pip/_internal/utils/unpacking.py
+++ b/contrib/python/pip/pip/_internal/utils/unpacking.py
@@ -1,5 +1,7 @@
"""Utilities related archives."""
+from __future__ import annotations
+
import logging
import os
import shutil
@@ -7,7 +9,7 @@ import stat
import sys
import tarfile
import zipfile
-from typing import Iterable, List, Optional
+from collections.abc import Iterable
from zipfile import ZipInfo
from pip._internal.exceptions import InstallationError
@@ -47,7 +49,7 @@ def current_umask() -> int:
return mask
-def split_leading_dir(path: str) -> List[str]:
+def split_leading_dir(path: str) -> list[str]:
path = path.lstrip("/").lstrip("\\")
if "/" in path and (
("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path
@@ -131,7 +133,7 @@ def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
"outside target directory ({})"
)
raise InstallationError(message.format(filename, fn, location))
- if fn.endswith("/") or fn.endswith("\\"):
+ if fn.endswith(("/", "\\")):
# A directory
ensure_dir(fn)
else:
@@ -307,7 +309,7 @@ def _untar_without_filter(
def unpack_file(
filename: str,
location: str,
- content_type: Optional[str] = None,
+ content_type: str | None = None,
) -> None:
filename = os.path.realpath(filename)
if (
diff --git a/contrib/python/pip/pip/_internal/utils/urls.py b/contrib/python/pip/pip/_internal/utils/urls.py
index 9f34f882a1a..e951a5e4e47 100644
--- a/contrib/python/pip/pip/_internal/utils/urls.py
+++ b/contrib/python/pip/pip/_internal/utils/urls.py
@@ -12,7 +12,7 @@ def path_to_url(path: str) -> str:
quoted path parts.
"""
path = os.path.normpath(os.path.abspath(path))
- url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path))
+ url = urllib.parse.urljoin("file://", urllib.request.pathname2url(path))
return url
diff --git a/contrib/python/pip/pip/_internal/utils/virtualenv.py b/contrib/python/pip/pip/_internal/utils/virtualenv.py
index 882e36f5c1d..b1742a3e9b1 100644
--- a/contrib/python/pip/pip/_internal/utils/virtualenv.py
+++ b/contrib/python/pip/pip/_internal/utils/virtualenv.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import logging
import os
import re
import site
import sys
-from typing import List, Optional
logger = logging.getLogger(__name__)
_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile(
@@ -33,7 +34,7 @@ def running_under_virtualenv() -> bool:
return _running_under_venv() or _running_under_legacy_virtualenv()
-def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
+def _get_pyvenv_cfg_lines() -> list[str] | None:
"""Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines
Returns None, if it could not read/access the file.
diff --git a/contrib/python/pip/pip/_internal/utils/wheel.py b/contrib/python/pip/pip/_internal/utils/wheel.py
index 70e186cdfd1..789e73629af 100644
--- a/contrib/python/pip/pip/_internal/utils/wheel.py
+++ b/contrib/python/pip/pip/_internal/utils/wheel.py
@@ -3,7 +3,6 @@
import logging
from email.message import Message
from email.parser import Parser
-from typing import Tuple
from zipfile import BadZipFile, ZipFile
from pip._vendor.packaging.utils import canonicalize_name
@@ -16,7 +15,7 @@ VERSION_COMPATIBLE = (1, 0)
logger = logging.getLogger(__name__)
-def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
+def parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]:
"""Extract information from the provided wheel, ensuring it meets basic
standards.
@@ -93,7 +92,7 @@ def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
return Parser().parsestr(wheel_text)
-def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
+def wheel_version(wheel_data: Message) -> tuple[int, ...]:
"""Given WHEEL metadata, return the parsed Wheel-Version.
Otherwise, raise UnsupportedWheel.
"""
@@ -109,7 +108,7 @@ def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
-def check_compatibility(version: Tuple[int, ...], name: str) -> None:
+def check_compatibility(version: tuple[int, ...], name: str) -> None:
"""Raises errors or warns if called with an incompatible Wheel-Version.
pip should refuse to install a Wheel-Version that's a major series
diff --git a/contrib/python/pip/pip/_internal/vcs/bazaar.py b/contrib/python/pip/pip/_internal/vcs/bazaar.py
index c754b7cc5c0..3a8a21e6251 100644
--- a/contrib/python/pip/pip/_internal/vcs/bazaar.py
+++ b/contrib/python/pip/pip/_internal/vcs/bazaar.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import logging
-from typing import List, Optional, Tuple
from pip._internal.utils.misc import HiddenText, display_path
from pip._internal.utils.subprocess import make_command
@@ -30,7 +31,7 @@ class Bazaar(VersionControl):
)
@staticmethod
- def get_base_rev_args(rev: str) -> List[str]:
+ def get_base_rev_args(rev: str) -> list[str]:
return ["-r", rev]
def fetch_new(
@@ -54,24 +55,41 @@ class Bazaar(VersionControl):
)
self.run_command(cmd_args)
- def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def switch(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
self.run_command(make_command("switch", url), cwd=dest)
- def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def update(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
+ flags = []
+
+ if verbosity <= 0:
+ flags.append("-q")
+
output = self.run_command(
make_command("info"), show_stdout=False, stdout_only=True, cwd=dest
)
if output.startswith("Standalone "):
# Older versions of pip used to create standalone branches.
# Convert the standalone branch to a checkout by calling "bzr bind".
- cmd_args = make_command("bind", "-q", url)
+ cmd_args = make_command("bind", *flags, url)
self.run_command(cmd_args, cwd=dest)
- cmd_args = make_command("update", "-q", rev_options.to_args())
+ cmd_args = make_command("update", *flags, rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
@classmethod
- def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
+ def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]:
# hotfix the URL scheme after removing bzr+ from bzr+ssh:// re-add it
url, rev, user_pass = super().get_url_rev_and_auth(url)
if url.startswith("ssh://"):
@@ -104,7 +122,7 @@ class Bazaar(VersionControl):
return revision.splitlines()[-1]
@classmethod
- def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
+ def is_commit_id_equal(cls, dest: str, name: str | None) -> bool:
"""Always assume the versions don't match"""
return False
diff --git a/contrib/python/pip/pip/_internal/vcs/git.py b/contrib/python/pip/pip/_internal/vcs/git.py
index 9c926e969f1..1769da791cb 100644
--- a/contrib/python/pip/pip/_internal/vcs/git.py
+++ b/contrib/python/pip/pip/_internal/vcs/git.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import logging
import os.path
import pathlib
@@ -5,7 +7,7 @@ import re
import urllib.parse
import urllib.request
from dataclasses import replace
-from typing import Any, List, Optional, Tuple
+from typing import Any
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.utils.misc import HiddenText, display_path, hide_url
@@ -74,7 +76,7 @@ class Git(VersionControl):
default_arg_rev = "HEAD"
@staticmethod
- def get_base_rev_args(rev: str) -> List[str]:
+ def get_base_rev_args(rev: str) -> list[str]:
return [rev]
@classmethod
@@ -100,7 +102,7 @@ class Git(VersionControl):
is_tag_or_branch = bool(self.get_revision_sha(dest, rev_options.rev)[0])
return not is_tag_or_branch
- def get_git_version(self) -> Tuple[int, ...]:
+ def get_git_version(self) -> tuple[int, ...]:
version = self.run_command(
["version"],
command_desc="git version",
@@ -114,7 +116,7 @@ class Git(VersionControl):
return (int(match.group(1)), int(match.group(2)))
@classmethod
- def get_current_branch(cls, location: str) -> Optional[str]:
+ def get_current_branch(cls, location: str) -> str | None:
"""
Return the current branch, or None if HEAD isn't at a branch
(e.g. detached HEAD).
@@ -139,7 +141,7 @@ class Git(VersionControl):
return None
@classmethod
- def get_revision_sha(cls, dest: str, rev: str) -> Tuple[Optional[str], bool]:
+ def get_revision_sha(cls, dest: str, rev: str) -> tuple[str | None, bool]:
"""
Return (sha_or_none, is_branch), where sha_or_none is a commit hash
if the revision names a remote branch or tag, otherwise None.
@@ -234,7 +236,7 @@ class Git(VersionControl):
# Do not show a warning for the common case of something that has
# the form of a Git commit hash.
if not looks_like_hash(rev):
- logger.warning(
+ logger.info(
"Did not find branch or tag '%s', assuming revision or ref.",
rev,
)
@@ -254,7 +256,7 @@ class Git(VersionControl):
return rev_options
@classmethod
- def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
+ def is_commit_id_equal(cls, dest: str, name: str | None) -> bool:
"""
Return whether the current commit hash equals the given name.
@@ -274,7 +276,7 @@ class Git(VersionControl):
rev_display = rev_options.to_display()
logger.info("Cloning %s%s to %s", url, rev_display, display_path(dest))
if verbosity <= 0:
- flags: Tuple[str, ...] = ("--quiet",)
+ flags: tuple[str, ...] = ("--quiet",)
elif verbosity == 1:
flags = ()
else:
@@ -329,31 +331,59 @@ class Git(VersionControl):
logger.info("Resolved %s to commit %s", url, rev_options.rev)
#: repo may contain submodules
- self.update_submodules(dest)
+ self.update_submodules(dest, verbosity=verbosity)
- def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def switch(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
self.run_command(
make_command("config", "remote.origin.url", url),
cwd=dest,
)
- cmd_args = make_command("checkout", "-q", rev_options.to_args())
+
+ extra_flags = []
+
+ if verbosity <= 0:
+ extra_flags.append("-q")
+
+ cmd_args = make_command("checkout", *extra_flags, rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
- self.update_submodules(dest)
+ self.update_submodules(dest, verbosity=verbosity)
+
+ def update(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
+ extra_flags = []
+
+ if verbosity <= 0:
+ extra_flags.append("-q")
- def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
# First fetch changes from the default remote
if self.get_git_version() >= (1, 9):
# fetch tags in addition to everything else
- self.run_command(["fetch", "-q", "--tags"], cwd=dest)
+ self.run_command(["fetch", "--tags", *extra_flags], cwd=dest)
else:
- self.run_command(["fetch", "-q"], cwd=dest)
+ self.run_command(["fetch", *extra_flags], cwd=dest)
# Then reset to wanted revision (maybe even origin/master)
rev_options = self.resolve_revision(dest, url, rev_options)
- cmd_args = make_command("reset", "--hard", "-q", rev_options.to_args())
+ cmd_args = make_command(
+ "reset",
+ "--hard",
+ *extra_flags,
+ rev_options.to_args(),
+ )
self.run_command(cmd_args, cwd=dest)
#: update submodules
- self.update_submodules(dest)
+ self.update_submodules(dest, verbosity=verbosity)
@classmethod
def get_remote_url(cls, location: str) -> str:
@@ -433,7 +463,7 @@ class Git(VersionControl):
return True
@classmethod
- def get_revision(cls, location: str, rev: Optional[str] = None) -> str:
+ def get_revision(cls, location: str, rev: str | None = None) -> str:
if rev is None:
rev = "HEAD"
current_rev = cls.run_command(
@@ -445,7 +475,7 @@ class Git(VersionControl):
return current_rev.strip()
@classmethod
- def get_subdirectory(cls, location: str) -> Optional[str]:
+ def get_subdirectory(cls, location: str) -> str | None:
"""
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
@@ -463,7 +493,7 @@ class Git(VersionControl):
return find_path_to_project_root_from_repo_root(location, repo_root)
@classmethod
- def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
+ def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]:
"""
Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
That's required because although they use SSH they sometimes don't
@@ -494,16 +524,21 @@ class Git(VersionControl):
return url, rev, user_pass
@classmethod
- def update_submodules(cls, location: str) -> None:
+ def update_submodules(cls, location: str, verbosity: int = 0) -> None:
+ argv = ["submodule", "update", "--init", "--recursive"]
+
+ if verbosity <= 0:
+ argv.append("-q")
+
if not os.path.exists(os.path.join(location, ".gitmodules")):
return
cls.run_command(
- ["submodule", "update", "--init", "--recursive", "-q"],
+ argv,
cwd=location,
)
@classmethod
- def get_repository_root(cls, location: str) -> Optional[str]:
+ def get_repository_root(cls, location: str) -> str | None:
loc = super().get_repository_root(location)
if loc:
return loc
diff --git a/contrib/python/pip/pip/_internal/vcs/mercurial.py b/contrib/python/pip/pip/_internal/vcs/mercurial.py
index c183d41d09c..c875803164e 100644
--- a/contrib/python/pip/pip/_internal/vcs/mercurial.py
+++ b/contrib/python/pip/pip/_internal/vcs/mercurial.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import configparser
import logging
import os
-from typing import List, Optional, Tuple
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.utils.misc import HiddenText, display_path
@@ -30,7 +31,7 @@ class Mercurial(VersionControl):
)
@staticmethod
- def get_base_rev_args(rev: str) -> List[str]:
+ def get_base_rev_args(rev: str) -> list[str]:
return [f"--rev={rev}"]
def fetch_new(
@@ -44,7 +45,7 @@ class Mercurial(VersionControl):
display_path(dest),
)
if verbosity <= 0:
- flags: Tuple[str, ...] = ("--quiet",)
+ flags: tuple[str, ...] = ("--quiet",)
elif verbosity == 1:
flags = ()
elif verbosity == 2:
@@ -57,9 +58,20 @@ class Mercurial(VersionControl):
cwd=dest,
)
- def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def switch(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
+ extra_flags = []
repo_config = os.path.join(dest, self.dirname, "hgrc")
config = configparser.RawConfigParser()
+
+ if verbosity <= 0:
+ extra_flags.append("-q")
+
try:
config.read(repo_config)
config.set("paths", "default", url.secret)
@@ -68,12 +80,23 @@ class Mercurial(VersionControl):
except (OSError, configparser.NoSectionError) as exc:
logger.warning("Could not switch Mercurial repository to %s: %s", url, exc)
else:
- cmd_args = make_command("update", "-q", rev_options.to_args())
+ cmd_args = make_command("update", *extra_flags, rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
- def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
- self.run_command(["pull", "-q"], cwd=dest)
- cmd_args = make_command("update", "-q", rev_options.to_args())
+ def update(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
+ extra_flags = []
+
+ if verbosity <= 0:
+ extra_flags.append("-q")
+
+ self.run_command(["pull", *extra_flags], cwd=dest)
+ cmd_args = make_command("update", *extra_flags, rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
@classmethod
@@ -116,12 +139,12 @@ class Mercurial(VersionControl):
return current_rev_hash
@classmethod
- def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
+ def is_commit_id_equal(cls, dest: str, name: str | None) -> bool:
"""Always assume the versions don't match"""
return False
@classmethod
- def get_subdirectory(cls, location: str) -> Optional[str]:
+ def get_subdirectory(cls, location: str) -> str | None:
"""
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
@@ -135,7 +158,7 @@ class Mercurial(VersionControl):
return find_path_to_project_root_from_repo_root(location, repo_root)
@classmethod
- def get_repository_root(cls, location: str) -> Optional[str]:
+ def get_repository_root(cls, location: str) -> str | None:
loc = super().get_repository_root(location)
if loc:
return loc
diff --git a/contrib/python/pip/pip/_internal/vcs/subversion.py b/contrib/python/pip/pip/_internal/vcs/subversion.py
index f359266d9c0..579f428c8ca 100644
--- a/contrib/python/pip/pip/_internal/vcs/subversion.py
+++ b/contrib/python/pip/pip/_internal/vcs/subversion.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import logging
import os
import re
-from typing import List, Optional, Tuple
from pip._internal.utils.misc import (
HiddenText,
@@ -38,7 +39,7 @@ class Subversion(VersionControl):
return True
@staticmethod
- def get_base_rev_args(rev: str) -> List[str]:
+ def get_base_rev_args(rev: str) -> list[str]:
return ["-r", rev]
@classmethod
@@ -73,7 +74,7 @@ class Subversion(VersionControl):
@classmethod
def get_netloc_and_auth(
cls, netloc: str, scheme: str
- ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]:
+ ) -> tuple[str, tuple[str | None, str | None]]:
"""
This override allows the auth information to be passed to svn via the
--username and --password options instead of via the URL.
@@ -86,7 +87,7 @@ class Subversion(VersionControl):
return split_auth_from_netloc(netloc)
@classmethod
- def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
+ def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]:
# hotfix the URL scheme after removing svn+ from svn+ssh:// re-add it
url, rev, user_pass = super().get_url_rev_and_auth(url)
if url.startswith("ssh://"):
@@ -94,9 +95,7 @@ class Subversion(VersionControl):
return url, rev, user_pass
@staticmethod
- def make_rev_args(
- username: Optional[str], password: Optional[HiddenText]
- ) -> CommandArgs:
+ def make_rev_args(username: str | None, password: HiddenText | None) -> CommandArgs:
extra_args: CommandArgs = []
if username:
extra_args += ["--username", username]
@@ -130,7 +129,7 @@ class Subversion(VersionControl):
return url
@classmethod
- def _get_svn_url_rev(cls, location: str) -> Tuple[Optional[str], int]:
+ def _get_svn_url_rev(cls, location: str) -> tuple[str | None, int]:
from pip._internal.exceptions import InstallationError
entries_path = os.path.join(location, cls.dirname, "entries")
@@ -141,7 +140,7 @@ class Subversion(VersionControl):
data = ""
url = None
- if data.startswith("8") or data.startswith("9") or data.startswith("10"):
+ if data.startswith(("8", "9", "10")):
entries = list(map(str.splitlines, data.split("\n\x0c\n")))
del entries[0][0] # get rid of the '8'
url = entries[0][3]
@@ -180,11 +179,11 @@ class Subversion(VersionControl):
return url, rev
@classmethod
- def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
+ def is_commit_id_equal(cls, dest: str, name: str | None) -> bool:
"""Always assume the versions don't match"""
return False
- def __init__(self, use_interactive: Optional[bool] = None) -> None:
+ def __init__(self, use_interactive: bool | None = None) -> None:
if use_interactive is None:
use_interactive = is_console_interactive()
self.use_interactive = use_interactive
@@ -194,11 +193,11 @@ class Subversion(VersionControl):
# Special value definitions:
# None: Not evaluated yet.
# Empty tuple: Could not parse version.
- self._vcs_version: Optional[Tuple[int, ...]] = None
+ self._vcs_version: tuple[int, ...] | None = None
super().__init__()
- def call_vcs_version(self) -> Tuple[int, ...]:
+ def call_vcs_version(self) -> tuple[int, ...]:
"""Query the version of the currently installed Subversion client.
:return: A tuple containing the parts of the version information or
@@ -226,7 +225,7 @@ class Subversion(VersionControl):
return parsed_version
- def get_vcs_version(self) -> Tuple[int, ...]:
+ def get_vcs_version(self) -> tuple[int, ...]:
"""Return the version of the currently installed Subversion client.
If the version of the Subversion client has already been queried,
@@ -301,7 +300,13 @@ class Subversion(VersionControl):
)
self.run_command(cmd_args)
- def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def switch(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
cmd_args = make_command(
"switch",
self.get_remote_call_options(),
@@ -311,7 +316,13 @@ class Subversion(VersionControl):
)
self.run_command(cmd_args)
- def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def update(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
cmd_args = make_command(
"update",
self.get_remote_call_options(),
diff --git a/contrib/python/pip/pip/_internal/vcs/versioncontrol.py b/contrib/python/pip/pip/_internal/vcs/versioncontrol.py
index a4133165e9a..4e91ccd4c4c 100644
--- a/contrib/python/pip/pip/_internal/vcs/versioncontrol.py
+++ b/contrib/python/pip/pip/_internal/vcs/versioncontrol.py
@@ -1,23 +1,18 @@
"""Handles all VCS (version control) support"""
+from __future__ import annotations
+
import logging
import os
import shutil
import sys
import urllib.parse
+from collections.abc import Iterable, Iterator, Mapping
from dataclasses import dataclass, field
from typing import (
Any,
- Dict,
- Iterable,
- Iterator,
- List,
Literal,
- Mapping,
Optional,
- Tuple,
- Type,
- Union,
)
from pip._internal.cli.spinners import SpinnerInterface
@@ -44,7 +39,7 @@ __all__ = ["vcs"]
logger = logging.getLogger(__name__)
-AuthInfo = Tuple[Optional[str], Optional[str]]
+AuthInfo = tuple[Optional[str], Optional[str]]
def is_url(name: str) -> bool:
@@ -58,7 +53,7 @@ def is_url(name: str) -> bool:
def make_vcs_requirement_url(
- repo_url: str, rev: str, project_name: str, subdir: Optional[str] = None
+ repo_url: str, rev: str, project_name: str, subdir: str | None = None
) -> str:
"""
Return the URL for a VCS requirement.
@@ -77,7 +72,7 @@ def make_vcs_requirement_url(
def find_path_to_project_root_from_repo_root(
location: str, repo_root: str
-) -> Optional[str]:
+) -> str | None:
"""
Find the the Python project's root by searching up the filesystem from
`location`. Return the path to project root relative to `repo_root`.
@@ -126,16 +121,16 @@ class RevOptions:
extra_args: a list of extra options.
"""
- vc_class: Type["VersionControl"]
- rev: Optional[str] = None
+ vc_class: type[VersionControl]
+ rev: str | None = None
extra_args: CommandArgs = field(default_factory=list)
- branch_name: Optional[str] = None
+ branch_name: str | None = None
def __repr__(self) -> str:
return f"<RevOptions {self.vc_class.name}: rev={self.rev!r}>"
@property
- def arg_rev(self) -> Optional[str]:
+ def arg_rev(self) -> str | None:
if self.rev is None:
return self.vc_class.default_arg_rev
@@ -159,7 +154,7 @@ class RevOptions:
return f" (to revision {self.rev})"
- def make_new(self, rev: str) -> "RevOptions":
+ def make_new(self, rev: str) -> RevOptions:
"""
Make a copy of the current instance, but with a new rev.
@@ -170,7 +165,7 @@ class RevOptions:
class VcsSupport:
- _registry: Dict[str, "VersionControl"] = {}
+ _registry: dict[str, VersionControl] = {}
schemes = ["ssh", "git", "hg", "bzr", "sftp", "svn"]
def __init__(self) -> None:
@@ -183,21 +178,21 @@ class VcsSupport:
return self._registry.__iter__()
@property
- def backends(self) -> List["VersionControl"]:
+ def backends(self) -> list[VersionControl]:
return list(self._registry.values())
@property
- def dirnames(self) -> List[str]:
+ def dirnames(self) -> list[str]:
return [backend.dirname for backend in self.backends]
@property
- def all_schemes(self) -> List[str]:
- schemes: List[str] = []
+ def all_schemes(self) -> list[str]:
+ schemes: list[str] = []
for backend in self.backends:
schemes.extend(backend.schemes)
return schemes
- def register(self, cls: Type["VersionControl"]) -> None:
+ def register(self, cls: type[VersionControl]) -> None:
if not hasattr(cls, "name"):
logger.warning("Cannot register VCS %s", cls.__name__)
return
@@ -209,7 +204,7 @@ class VcsSupport:
if name in self._registry:
del self._registry[name]
- def get_backend_for_dir(self, location: str) -> Optional["VersionControl"]:
+ def get_backend_for_dir(self, location: str) -> VersionControl | None:
"""
Return a VersionControl object if a repository of that type is found
at the given directory.
@@ -232,7 +227,7 @@ class VcsSupport:
inner_most_repo_path = max(vcs_backends, key=len)
return vcs_backends[inner_most_repo_path]
- def get_backend_for_scheme(self, scheme: str) -> Optional["VersionControl"]:
+ def get_backend_for_scheme(self, scheme: str) -> VersionControl | None:
"""
Return a VersionControl object or None.
"""
@@ -241,7 +236,7 @@ class VcsSupport:
return vcs_backend
return None
- def get_backend(self, name: str) -> Optional["VersionControl"]:
+ def get_backend(self, name: str) -> VersionControl | None:
"""
Return a VersionControl object or None.
"""
@@ -257,10 +252,10 @@ class VersionControl:
dirname = ""
repo_name = ""
# List of supported schemes for this Version Control
- schemes: Tuple[str, ...] = ()
+ schemes: tuple[str, ...] = ()
# Iterable of environment variable names to pass to call_subprocess().
- unset_environ: Tuple[str, ...] = ()
- default_arg_rev: Optional[str] = None
+ unset_environ: tuple[str, ...] = ()
+ default_arg_rev: str | None = None
@classmethod
def should_add_vcs_url_prefix(cls, remote_url: str) -> bool:
@@ -271,7 +266,7 @@ class VersionControl:
return not remote_url.lower().startswith(f"{cls.name}:")
@classmethod
- def get_subdirectory(cls, location: str) -> Optional[str]:
+ def get_subdirectory(cls, location: str) -> str | None:
"""
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
@@ -310,7 +305,7 @@ class VersionControl:
return req
@staticmethod
- def get_base_rev_args(rev: str) -> List[str]:
+ def get_base_rev_args(rev: str) -> list[str]:
"""
Return the base revision arguments for a vcs command.
@@ -334,7 +329,7 @@ class VersionControl:
@classmethod
def make_rev_options(
- cls, rev: Optional[str] = None, extra_args: Optional[CommandArgs] = None
+ cls, rev: str | None = None, extra_args: CommandArgs | None = None
) -> RevOptions:
"""
Return a RevOptions object.
@@ -357,7 +352,7 @@ class VersionControl:
@classmethod
def get_netloc_and_auth(
cls, netloc: str, scheme: str
- ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]:
+ ) -> tuple[str, tuple[str | None, str | None]]:
"""
Parse the repository URL's netloc, and return the new netloc to use
along with auth information.
@@ -376,7 +371,7 @@ class VersionControl:
return netloc, (None, None)
@classmethod
- def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
+ def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]:
"""
Parse the repository URL to use, and return the URL, revision,
and auth info to use.
@@ -406,22 +401,20 @@ class VersionControl:
return url, rev, user_pass
@staticmethod
- def make_rev_args(
- username: Optional[str], password: Optional[HiddenText]
- ) -> CommandArgs:
+ def make_rev_args(username: str | None, password: HiddenText | None) -> CommandArgs:
"""
Return the RevOptions "extra arguments" to use in obtain().
"""
return []
- def get_url_rev_options(self, url: HiddenText) -> Tuple[HiddenText, RevOptions]:
+ def get_url_rev_options(self, url: HiddenText) -> tuple[HiddenText, RevOptions]:
"""
Return the URL and RevOptions object to use in obtain(),
as a tuple (url, rev_options).
"""
secret_url, rev, user_pass = self.get_url_rev_and_auth(url.secret)
username, secret_password = user_pass
- password: Optional[HiddenText] = None
+ password: HiddenText | None = None
if secret_password is not None:
password = hide_value(secret_password)
extra_args = self.make_rev_args(username, password)
@@ -458,7 +451,13 @@ class VersionControl:
"""
raise NotImplementedError
- def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def switch(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
"""
Switch the repo at ``dest`` to point to ``URL``.
@@ -467,7 +466,13 @@ class VersionControl:
"""
raise NotImplementedError
- def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ def update(
+ self,
+ dest: str,
+ url: HiddenText,
+ rev_options: RevOptions,
+ verbosity: int = 0,
+ ) -> None:
"""
Update an already-existing repo to the given ``rev_options``.
@@ -477,7 +482,7 @@ class VersionControl:
raise NotImplementedError
@classmethod
- def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
+ def is_commit_id_equal(cls, dest: str, name: str | None) -> bool:
"""
Return whether the id of the current commit equals the given name.
@@ -519,7 +524,7 @@ class VersionControl:
self.repo_name,
rev_display,
)
- self.update(dest, url, rev_options)
+ self.update(dest, url, rev_options, verbosity=verbosity)
else:
logger.info("Skipping because already up-to-date.")
return
@@ -574,7 +579,7 @@ class VersionControl:
url,
rev_display,
)
- self.switch(dest, url, rev_options)
+ self.switch(dest, url, rev_options, verbosity=verbosity)
def unpack(self, location: str, url: HiddenText, verbosity: int) -> None:
"""
@@ -608,14 +613,14 @@ class VersionControl:
@classmethod
def run_command(
cls,
- cmd: Union[List[str], CommandArgs],
+ cmd: list[str] | CommandArgs,
show_stdout: bool = True,
- cwd: Optional[str] = None,
- on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
- extra_ok_returncodes: Optional[Iterable[int]] = None,
- command_desc: Optional[str] = None,
- extra_environ: Optional[Mapping[str, Any]] = None,
- spinner: Optional[SpinnerInterface] = None,
+ cwd: str | None = None,
+ on_returncode: Literal["raise", "warn", "ignore"] = "raise",
+ extra_ok_returncodes: Iterable[int] | None = None,
+ command_desc: str | None = None,
+ extra_environ: Mapping[str, Any] | None = None,
+ spinner: SpinnerInterface | None = None,
log_failed_cmd: bool = True,
stdout_only: bool = False,
) -> str:
@@ -672,7 +677,7 @@ class VersionControl:
return os.path.exists(os.path.join(path, cls.dirname))
@classmethod
- def get_repository_root(cls, location: str) -> Optional[str]:
+ def get_repository_root(cls, location: str) -> str | None:
"""
Return the "root" (top-level) directory controlled by the vcs,
or `None` if the directory is not in any.
diff --git a/contrib/python/pip/pip/_internal/wheel_builder.py b/contrib/python/pip/pip/_internal/wheel_builder.py
index 3cf02e01d98..beed02f00c5 100644
--- a/contrib/python/pip/pip/_internal/wheel_builder.py
+++ b/contrib/python/pip/pip/_internal/wheel_builder.py
@@ -1,10 +1,12 @@
"""Orchestrator for building wheels from InstallRequirements."""
+from __future__ import annotations
+
import logging
import os.path
import re
import shutil
-from typing import Iterable, List, Optional, Tuple
+from collections.abc import Iterable
from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version
from pip._vendor.packaging.version import InvalidVersion, Version
@@ -30,7 +32,7 @@ logger = logging.getLogger(__name__)
_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)
-BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]
+BuildResult = tuple[list[InstallRequirement], list[InstallRequirement]]
def _contains_egg_info(s: str) -> bool:
@@ -67,7 +69,7 @@ def should_build_for_install_command(
def _should_cache(
req: InstallRequirement,
-) -> Optional[bool]:
+) -> bool | None:
"""
Return whether a built InstallRequirement can be stored in the persistent
wheel cache, assuming the wheel cache is available, and _should_build()
@@ -146,10 +148,10 @@ def _build_one(
req: InstallRequirement,
output_dir: str,
verify: bool,
- build_options: List[str],
- global_options: List[str],
+ build_options: list[str],
+ global_options: list[str],
editable: bool,
-) -> Optional[str]:
+) -> str | None:
"""Build one wheel.
:return: The filename of the built wheel, or None if the build failed.
@@ -183,10 +185,10 @@ def _build_one(
def _build_one_inside_env(
req: InstallRequirement,
output_dir: str,
- build_options: List[str],
- global_options: List[str],
+ build_options: list[str],
+ global_options: list[str],
editable: bool,
-) -> Optional[str]:
+) -> str | None:
with TempDirectory(kind="wheel") as temp_dir:
assert req.name
if req.use_pep517:
@@ -251,7 +253,7 @@ def _build_one_inside_env(
return None
-def _clean_one_legacy(req: InstallRequirement, global_options: List[str]) -> bool:
+def _clean_one_legacy(req: InstallRequirement, global_options: list[str]) -> bool:
clean_args = make_setuptools_clean_args(
req.setup_py_path,
global_options=global_options,
@@ -272,8 +274,8 @@ def build(
requirements: Iterable[InstallRequirement],
wheel_cache: WheelCache,
verify: bool,
- build_options: List[str],
- global_options: List[str],
+ build_options: list[str],
+ global_options: list[str],
) -> BuildResult:
"""Build wheels.
diff --git a/contrib/python/pip/pip/_vendor/cachecontrol/__init__.py b/contrib/python/pip/pip/_vendor/cachecontrol/__init__.py
index 7be1e04f526..67888db0e41 100644
--- a/contrib/python/pip/pip/_vendor/cachecontrol/__init__.py
+++ b/contrib/python/pip/pip/_vendor/cachecontrol/__init__.py
@@ -9,7 +9,7 @@ Make it easy to import from cachecontrol without long namespaces.
__author__ = "Eric Larson"
__email__ = "[email protected]"
-__version__ = "0.14.2"
+__version__ = "0.14.3"
from pip._vendor.cachecontrol.adapter import CacheControlAdapter
from pip._vendor.cachecontrol.controller import CacheController
diff --git a/contrib/python/pip/pip/_vendor/certifi/__init__.py b/contrib/python/pip/pip/_vendor/certifi/__init__.py
index 177082e0fb1..e8370493f2e 100644
--- a/contrib/python/pip/pip/_vendor/certifi/__init__.py
+++ b/contrib/python/pip/pip/_vendor/certifi/__init__.py
@@ -1,4 +1,4 @@
from .core import contents, where
__all__ = ["contents", "where"]
-__version__ = "2025.01.31"
+__version__ = "2025.07.14"
diff --git a/contrib/python/pip/pip/_vendor/certifi/cacert.pem b/contrib/python/pip/pip/_vendor/certifi/cacert.pem
index 860f259bd7e..64c05d7f30a 100644
--- a/contrib/python/pip/pip/_vendor/certifi/cacert.pem
+++ b/contrib/python/pip/pip/_vendor/certifi/cacert.pem
@@ -1,95 +1,4 @@
-# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
-# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
-# Label: "GlobalSign Root CA"
-# Serial: 4835703278459707669005204
-# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a
-# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c
-# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99
------BEGIN CERTIFICATE-----
-MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
-A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
-b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
-MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
-YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
-aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
-jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
-xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
-1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
-snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
-U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
-9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
-BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
-AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
-yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
-38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
-AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
-DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
-HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
------END CERTIFICATE-----
-
-# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
-# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
-# Label: "Entrust.net Premium 2048 Secure Server CA"
-# Serial: 946069240
-# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90
-# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31
-# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77
------BEGIN CERTIFICATE-----
-MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
-RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
-bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
-IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
-ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
-MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
-LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
-YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
-A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
-K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
-sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
-MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
-XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
-HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
-4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
-HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
-j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
-U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
-zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
-u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
-bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
-fF6adulZkMV8gzURZVE=
------END CERTIFICATE-----
-
-# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust
-# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust
-# Label: "Baltimore CyberTrust Root"
-# Serial: 33554617
-# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4
-# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74
-# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb
------BEGIN CERTIFICATE-----
-MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
-RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
-VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
-DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
-ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
-VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
-mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
-IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
-mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
-XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
-dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
-jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
-BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
-DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
-9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
-jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
-Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
-ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
-R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
------END CERTIFICATE-----
-
# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
# Label: "Entrust Root Certification Authority"
@@ -125,39 +34,6 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
-# Issuer: CN=AAA Certificate Services O=Comodo CA Limited
-# Subject: CN=AAA Certificate Services O=Comodo CA Limited
-# Label: "Comodo AAA Services root"
-# Serial: 1
-# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0
-# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49
-# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4
------BEGIN CERTIFICATE-----
-MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
-MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
-GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
-YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
-MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
-BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
-GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
-BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
-3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
-YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
-rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
-ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
-oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
-MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
-QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
-b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
-AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
-GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
-Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
-G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
-l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
-smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
------END CERTIFICATE-----
-
# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited
# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited
# Label: "QuoVadis Root CA 2"
@@ -245,103 +121,6 @@ mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----
-# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
-# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
-# Label: "XRamp Global CA Root"
-# Serial: 107108908803651509692980124233745014957
-# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1
-# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6
-# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2
------BEGIN CERTIFICATE-----
-MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
-gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
-MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
-UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
-NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
-dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
-dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
-dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
-38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
-KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
-DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
-qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
-JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
-PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
-BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
-jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
-eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
-ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
-vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
-qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
-IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
-i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
-O+7ETPTsJ3xCwnR8gooJybQDJbw=
------END CERTIFICATE-----
-
-# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
-# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
-# Label: "Go Daddy Class 2 CA"
-# Serial: 0
-# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67
-# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4
-# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4
------BEGIN CERTIFICATE-----
-MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
-MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
-YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
-MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
-ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
-MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
-ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
-PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
-wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
-EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
-avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
-YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
-sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
-/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
-IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
-ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
-OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
-TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
-HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
-dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
-ReYNnyicsbkqWletNw+vHX/bvZ8=
------END CERTIFICATE-----
-
-# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
-# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
-# Label: "Starfield Class 2 CA"
-# Serial: 0
-# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24
-# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a
-# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58
------BEGIN CERTIFICATE-----
-MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
-MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
-U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
-NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
-ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
-ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
-DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
-8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
-+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
-X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
-K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
-1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
-A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
-zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
-YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
-bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
-DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
-L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
-eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
-xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
-VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
-WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
------END CERTIFICATE-----
-
# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
# Label: "DigiCert Assured ID Root CA"
@@ -4855,6 +4634,68 @@ knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ
hJ65bvspmZDogNOfJA==
-----END CERTIFICATE-----
+# Issuer: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc.
+# Subject: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc.
+# Label: "TrustAsia TLS ECC Root CA"
+# Serial: 310892014698942880364840003424242768478804666567
+# MD5 Fingerprint: 09:48:04:77:d2:fc:65:93:71:66:b1:11:95:4f:06:8c
+# SHA1 Fingerprint: b5:ec:39:f3:a1:66:37:ae:c3:05:94:57:e2:be:11:be:b7:a1:7f:36
+# SHA256 Fingerprint: c0:07:6b:9e:f0:53:1f:b1:a6:56:d6:7c:4e:be:97:cd:5d:ba:a4:1e:f4:45:98:ac:c2:48:98:78:c9:2d:87:11
+-----BEGIN CERTIFICATE-----
+MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw
+WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
+IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw
+NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE
+ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB
+c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/
+AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp
+guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw
+DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01
+L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR
+OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc.
+# Subject: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc.
+# Label: "TrustAsia TLS RSA Root CA"
+# Serial: 160405846464868906657516898462547310235378010780
+# MD5 Fingerprint: 3b:9e:c3:86:0f:34:3c:6b:c5:46:c4:8e:1d:e7:19:12
+# SHA1 Fingerprint: a5:46:50:c5:62:ea:95:9a:1a:a7:04:6f:17:58:c7:29:53:3d:03:fa
+# SHA256 Fingerprint: 06:c0:8d:7d:af:d8:76:97:1e:b1:12:4f:e6:7f:84:7e:c0:c7:a1:58:d3:ea:53:cb:e9:40:e2:ea:97:91:f4:c3
+-----BEGIN CERTIFICATE-----
+MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM
+BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp
+ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN
+MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG
+A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1
+c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+
+NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ
+Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561
+HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32
+ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb
+xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX
+i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ
+UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j
+TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT
+bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8
+S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT
+MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3
+Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4
+iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt
+7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp
+2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ
+g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj
+pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M
+pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP
+XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe
+SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0
+ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy
+323imttUQ/hHWKNddBWcwauwxzQ=
+-----END CERTIFICATE-----
+
# Issuer: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH
# Subject: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH
# Label: "D-TRUST EV Root CA 2 2023"
@@ -4895,3 +4736,43 @@ gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst
Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh
XBxvWHZks/wCuPWdCg==
-----END CERTIFICATE-----
+
+# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG
+# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG
+# Label: "SwissSign RSA TLS Root CA 2022 - 1"
+# Serial: 388078645722908516278762308316089881486363258315
+# MD5 Fingerprint: 16:2e:e4:19:76:81:85:ba:8e:91:58:f1:15:ef:72:39
+# SHA1 Fingerprint: 81:34:0a:be:4c:cd:ce:cc:e7:7d:cc:8a:d4:57:e2:45:a0:77:5d:ce
+# SHA256 Fingerprint: 19:31:44:f4:31:e0:fd:db:74:07:17:d4:de:92:6a:57:11:33:88:4b:43:60:d3:0e:27:29:13:cb:e6:60:ce:41
+-----BEGIN CERTIFICATE-----
+MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL
+BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE
+AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx
+MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT
+d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg
+MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX
+vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7
+LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX
+5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE
+EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt
+/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x
+0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5
+KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM
+0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd
+OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta
+clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK
+wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4
+DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL
+BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3
+10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz
+Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ
+iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc
+gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM
+ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF
+LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp
+zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td
+Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0
+rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO
+gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ
+-----END CERTIFICATE-----
diff --git a/contrib/python/pip/pip/_vendor/certifi/core.py b/contrib/python/pip/pip/_vendor/certifi/core.py
index 70e0c3bdbd2..2f2f7e088ae 100644
--- a/contrib/python/pip/pip/_vendor/certifi/core.py
+++ b/contrib/python/pip/pip/_vendor/certifi/core.py
@@ -46,7 +46,7 @@ if sys.version_info >= (3, 11):
def contents() -> str:
return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii")
-elif sys.version_info >= (3, 7):
+else:
from importlib.resources import path as get_path, read_text
@@ -81,34 +81,3 @@ elif sys.version_info >= (3, 7):
def contents() -> str:
return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
-
-else:
- import os
- import types
- from typing import Union
-
- Package = Union[types.ModuleType, str]
- Resource = Union[str, "os.PathLike"]
-
- # This fallback will work for Python versions prior to 3.7 that lack the
- # importlib.resources module but relies on the existing `where` function
- # so won't address issues with environments like PyOxidizer that don't set
- # __file__ on modules.
- def read_text(
- package: Package,
- resource: Resource,
- encoding: str = 'utf-8',
- errors: str = 'strict'
- ) -> str:
- with open(where(), encoding=encoding) as data:
- return data.read()
-
- # If we don't have importlib.resources, then we will just do the old logic
- # of assuming we're on the filesystem and munge the path directly.
- def where() -> str:
- f = os.path.dirname(__file__)
-
- return os.path.join(f, "cacert.pem")
-
- def contents() -> str:
- return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
diff --git a/contrib/python/pip/pip/_vendor/distlib/__init__.py b/contrib/python/pip/pip/_vendor/distlib/__init__.py
index bf0d6c6d30e..4e82943ef85 100644
--- a/contrib/python/pip/pip/_vendor/distlib/__init__.py
+++ b/contrib/python/pip/pip/_vendor/distlib/__init__.py
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2012-2023 Vinay Sajip.
+# Copyright (C) 2012-2024 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import logging
-__version__ = '0.3.9'
+__version__ = '0.4.0'
class DistlibException(Exception):
diff --git a/contrib/python/pip/pip/_vendor/distlib/database.py b/contrib/python/pip/pip/_vendor/distlib/database.py
deleted file mode 100644
index c0f896a7d85..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/database.py
+++ /dev/null
@@ -1,1329 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012-2023 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""PEP 376 implementation."""
-
-from __future__ import unicode_literals
-
-import base64
-import codecs
-import contextlib
-import hashlib
-import logging
-import os
-import posixpath
-import sys
-import zipimport
-
-from . import DistlibException, resources
-from .compat import StringIO
-from .version import get_scheme, UnsupportedVersionError
-from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME)
-from .util import (parse_requirement, cached_property, parse_name_and_version, read_exports, write_exports, CSVReader,
- CSVWriter)
-
-__all__ = [
- 'Distribution', 'BaseInstalledDistribution', 'InstalledDistribution', 'EggInfoDistribution', 'DistributionPath'
-]
-
-logger = logging.getLogger(__name__)
-
-EXPORTS_FILENAME = 'pydist-exports.json'
-COMMANDS_FILENAME = 'pydist-commands.json'
-
-DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', 'RESOURCES', EXPORTS_FILENAME, 'SHARED')
-
-DISTINFO_EXT = '.dist-info'
-
-
-class _Cache(object):
- """
- A simple cache mapping names and .dist-info paths to distributions
- """
-
- def __init__(self):
- """
- Initialise an instance. There is normally one for each DistributionPath.
- """
- self.name = {}
- self.path = {}
- self.generated = False
-
- def clear(self):
- """
- Clear the cache, setting it to its initial state.
- """
- self.name.clear()
- self.path.clear()
- self.generated = False
-
- def add(self, dist):
- """
- Add a distribution to the cache.
- :param dist: The distribution to add.
- """
- if dist.path not in self.path:
- self.path[dist.path] = dist
- self.name.setdefault(dist.key, []).append(dist)
-
-
-class DistributionPath(object):
- """
- Represents a set of distributions installed on a path (typically sys.path).
- """
-
- def __init__(self, path=None, include_egg=False):
- """
- Create an instance from a path, optionally including legacy (distutils/
- setuptools/distribute) distributions.
- :param path: The path to use, as a list of directories. If not specified,
- sys.path is used.
- :param include_egg: If True, this instance will look for and return legacy
- distributions as well as those based on PEP 376.
- """
- if path is None:
- path = sys.path
- self.path = path
- self._include_dist = True
- self._include_egg = include_egg
-
- self._cache = _Cache()
- self._cache_egg = _Cache()
- self._cache_enabled = True
- self._scheme = get_scheme('default')
-
- def _get_cache_enabled(self):
- return self._cache_enabled
-
- def _set_cache_enabled(self, value):
- self._cache_enabled = value
-
- cache_enabled = property(_get_cache_enabled, _set_cache_enabled)
-
- def clear_cache(self):
- """
- Clears the internal cache.
- """
- self._cache.clear()
- self._cache_egg.clear()
-
- def _yield_distributions(self):
- """
- Yield .dist-info and/or .egg(-info) distributions.
- """
- # We need to check if we've seen some resources already, because on
- # some Linux systems (e.g. some Debian/Ubuntu variants) there are
- # symlinks which alias other files in the environment.
- seen = set()
- for path in self.path:
- finder = resources.finder_for_path(path)
- if finder is None:
- continue
- r = finder.find('')
- if not r or not r.is_container:
- continue
- rset = sorted(r.resources)
- for entry in rset:
- r = finder.find(entry)
- if not r or r.path in seen:
- continue
- try:
- if self._include_dist and entry.endswith(DISTINFO_EXT):
- possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
- for metadata_filename in possible_filenames:
- metadata_path = posixpath.join(entry, metadata_filename)
- pydist = finder.find(metadata_path)
- if pydist:
- break
- else:
- continue
-
- with contextlib.closing(pydist.as_stream()) as stream:
- metadata = Metadata(fileobj=stream, scheme='legacy')
- logger.debug('Found %s', r.path)
- seen.add(r.path)
- yield new_dist_class(r.path, metadata=metadata, env=self)
- elif self._include_egg and entry.endswith(('.egg-info', '.egg')):
- logger.debug('Found %s', r.path)
- seen.add(r.path)
- yield old_dist_class(r.path, self)
- except Exception as e:
- msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s'
- logger.warning(msg, r.path, e)
- import warnings
- warnings.warn(msg % (r.path, e), stacklevel=2)
-
- def _generate_cache(self):
- """
- Scan the path for distributions and populate the cache with
- those that are found.
- """
- gen_dist = not self._cache.generated
- gen_egg = self._include_egg and not self._cache_egg.generated
- if gen_dist or gen_egg:
- for dist in self._yield_distributions():
- if isinstance(dist, InstalledDistribution):
- self._cache.add(dist)
- else:
- self._cache_egg.add(dist)
-
- if gen_dist:
- self._cache.generated = True
- if gen_egg:
- self._cache_egg.generated = True
-
- @classmethod
- def distinfo_dirname(cls, name, version):
- """
- The *name* and *version* parameters are converted into their
- filename-escaped form, i.e. any ``'-'`` characters are replaced
- with ``'_'`` other than the one in ``'dist-info'`` and the one
- separating the name from the version number.
-
- :parameter name: is converted to a standard distribution name by replacing
- any runs of non- alphanumeric characters with a single
- ``'-'``.
- :type name: string
- :parameter version: is converted to a standard version string. Spaces
- become dots, and all other non-alphanumeric characters
- (except dots) become dashes, with runs of multiple
- dashes condensed to a single dash.
- :type version: string
- :returns: directory name
- :rtype: string"""
- name = name.replace('-', '_')
- return '-'.join([name, version]) + DISTINFO_EXT
-
- def get_distributions(self):
- """
- Provides an iterator that looks for distributions and returns
- :class:`InstalledDistribution` or
- :class:`EggInfoDistribution` instances for each one of them.
-
- :rtype: iterator of :class:`InstalledDistribution` and
- :class:`EggInfoDistribution` instances
- """
- if not self._cache_enabled:
- for dist in self._yield_distributions():
- yield dist
- else:
- self._generate_cache()
-
- for dist in self._cache.path.values():
- yield dist
-
- if self._include_egg:
- for dist in self._cache_egg.path.values():
- yield dist
-
- def get_distribution(self, name):
- """
- Looks for a named distribution on the path.
-
- This function only returns the first result found, as no more than one
- value is expected. If nothing is found, ``None`` is returned.
-
- :rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution`
- or ``None``
- """
- result = None
- name = name.lower()
- if not self._cache_enabled:
- for dist in self._yield_distributions():
- if dist.key == name:
- result = dist
- break
- else:
- self._generate_cache()
-
- if name in self._cache.name:
- result = self._cache.name[name][0]
- elif self._include_egg and name in self._cache_egg.name:
- result = self._cache_egg.name[name][0]
- return result
-
- def provides_distribution(self, name, version=None):
- """
- Iterates over all distributions to find which distributions provide *name*.
- If a *version* is provided, it will be used to filter the results.
-
- This function only returns the first result found, since no more than
- one values are expected. If the directory is not found, returns ``None``.
-
- :parameter version: a version specifier that indicates the version
- required, conforming to the format in ``PEP-345``
-
- :type name: string
- :type version: string
- """
- matcher = None
- if version is not None:
- try:
- matcher = self._scheme.matcher('%s (%s)' % (name, version))
- except ValueError:
- raise DistlibException('invalid name or version: %r, %r' % (name, version))
-
- for dist in self.get_distributions():
- # We hit a problem on Travis where enum34 was installed and doesn't
- # have a provides attribute ...
- if not hasattr(dist, 'provides'):
- logger.debug('No "provides": %s', dist)
- else:
- provided = dist.provides
-
- for p in provided:
- p_name, p_ver = parse_name_and_version(p)
- if matcher is None:
- if p_name == name:
- yield dist
- break
- else:
- if p_name == name and matcher.match(p_ver):
- yield dist
- break
-
- def get_file_path(self, name, relative_path):
- """
- Return the path to a resource file.
- """
- dist = self.get_distribution(name)
- if dist is None:
- raise LookupError('no distribution named %r found' % name)
- return dist.get_resource_path(relative_path)
-
- def get_exported_entries(self, category, name=None):
- """
- Return all of the exported entries in a particular category.
-
- :param category: The category to search for entries.
- :param name: If specified, only entries with that name are returned.
- """
- for dist in self.get_distributions():
- r = dist.exports
- if category in r:
- d = r[category]
- if name is not None:
- if name in d:
- yield d[name]
- else:
- for v in d.values():
- yield v
-
-
-class Distribution(object):
- """
- A base class for distributions, whether installed or from indexes.
- Either way, it must have some metadata, so that's all that's needed
- for construction.
- """
-
- build_time_dependency = False
- """
- Set to True if it's known to be only a build-time dependency (i.e.
- not needed after installation).
- """
-
- requested = False
- """A boolean that indicates whether the ``REQUESTED`` metadata file is
- present (in other words, whether the package was installed by user
- request or it was installed as a dependency)."""
-
- def __init__(self, metadata):
- """
- Initialise an instance.
- :param metadata: The instance of :class:`Metadata` describing this
- distribution.
- """
- self.metadata = metadata
- self.name = metadata.name
- self.key = self.name.lower() # for case-insensitive comparisons
- self.version = metadata.version
- self.locator = None
- self.digest = None
- self.extras = None # additional features requested
- self.context = None # environment marker overrides
- self.download_urls = set()
- self.digests = {}
-
- @property
- def source_url(self):
- """
- The source archive download URL for this distribution.
- """
- return self.metadata.source_url
-
- download_url = source_url # Backward compatibility
-
- @property
- def name_and_version(self):
- """
- A utility property which displays the name and version in parentheses.
- """
- return '%s (%s)' % (self.name, self.version)
-
- @property
- def provides(self):
- """
- A set of distribution names and versions provided by this distribution.
- :return: A set of "name (version)" strings.
- """
- plist = self.metadata.provides
- s = '%s (%s)' % (self.name, self.version)
- if s not in plist:
- plist.append(s)
- return plist
-
- def _get_requirements(self, req_attr):
- md = self.metadata
- reqts = getattr(md, req_attr)
- logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr, reqts)
- return set(md.get_requirements(reqts, extras=self.extras, env=self.context))
-
- @property
- def run_requires(self):
- return self._get_requirements('run_requires')
-
- @property
- def meta_requires(self):
- return self._get_requirements('meta_requires')
-
- @property
- def build_requires(self):
- return self._get_requirements('build_requires')
-
- @property
- def test_requires(self):
- return self._get_requirements('test_requires')
-
- @property
- def dev_requires(self):
- return self._get_requirements('dev_requires')
-
- def matches_requirement(self, req):
- """
- Say if this instance matches (fulfills) a requirement.
- :param req: The requirement to match.
- :rtype req: str
- :return: True if it matches, else False.
- """
- # Requirement may contain extras - parse to lose those
- # from what's passed to the matcher
- r = parse_requirement(req)
- scheme = get_scheme(self.metadata.scheme)
- try:
- matcher = scheme.matcher(r.requirement)
- except UnsupportedVersionError:
- # XXX compat-mode if cannot read the version
- logger.warning('could not read version %r - using name only', req)
- name = req.split()[0]
- matcher = scheme.matcher(name)
-
- name = matcher.key # case-insensitive
-
- result = False
- for p in self.provides:
- p_name, p_ver = parse_name_and_version(p)
- if p_name != name:
- continue
- try:
- result = matcher.match(p_ver)
- break
- except UnsupportedVersionError:
- pass
- return result
-
- def __repr__(self):
- """
- Return a textual representation of this instance,
- """
- if self.source_url:
- suffix = ' [%s]' % self.source_url
- else:
- suffix = ''
- return '<Distribution %s (%s)%s>' % (self.name, self.version, suffix)
-
- def __eq__(self, other):
- """
- See if this distribution is the same as another.
- :param other: The distribution to compare with. To be equal to one
- another. distributions must have the same type, name,
- version and source_url.
- :return: True if it is the same, else False.
- """
- if type(other) is not type(self):
- result = False
- else:
- result = (self.name == other.name and self.version == other.version and self.source_url == other.source_url)
- return result
-
- def __hash__(self):
- """
- Compute hash in a way which matches the equality test.
- """
- return hash(self.name) + hash(self.version) + hash(self.source_url)
-
-
-class BaseInstalledDistribution(Distribution):
- """
- This is the base class for installed distributions (whether PEP 376 or
- legacy).
- """
-
- hasher = None
-
- def __init__(self, metadata, path, env=None):
- """
- Initialise an instance.
- :param metadata: An instance of :class:`Metadata` which describes the
- distribution. This will normally have been initialised
- from a metadata file in the ``path``.
- :param path: The path of the ``.dist-info`` or ``.egg-info``
- directory for the distribution.
- :param env: This is normally the :class:`DistributionPath`
- instance where this distribution was found.
- """
- super(BaseInstalledDistribution, self).__init__(metadata)
- self.path = path
- self.dist_path = env
-
- def get_hash(self, data, hasher=None):
- """
- Get the hash of some data, using a particular hash algorithm, if
- specified.
-
- :param data: The data to be hashed.
- :type data: bytes
- :param hasher: The name of a hash implementation, supported by hashlib,
- or ``None``. Examples of valid values are ``'sha1'``,
- ``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and
- ``'sha512'``. If no hasher is specified, the ``hasher``
- attribute of the :class:`InstalledDistribution` instance
- is used. If the hasher is determined to be ``None``, MD5
- is used as the hashing algorithm.
- :returns: The hash of the data. If a hasher was explicitly specified,
- the returned hash will be prefixed with the specified hasher
- followed by '='.
- :rtype: str
- """
- if hasher is None:
- hasher = self.hasher
- if hasher is None:
- hasher = hashlib.md5
- prefix = ''
- else:
- hasher = getattr(hashlib, hasher)
- prefix = '%s=' % self.hasher
- digest = hasher(data).digest()
- digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii')
- return '%s%s' % (prefix, digest)
-
-
-class InstalledDistribution(BaseInstalledDistribution):
- """
- Created with the *path* of the ``.dist-info`` directory provided to the
- constructor. It reads the metadata contained in ``pydist.json`` when it is
- instantiated., or uses a passed in Metadata instance (useful for when
- dry-run mode is being used).
- """
-
- hasher = 'sha256'
-
- def __init__(self, path, metadata=None, env=None):
- self.modules = []
- self.finder = finder = resources.finder_for_path(path)
- if finder is None:
- raise ValueError('finder unavailable for %s' % path)
- if env and env._cache_enabled and path in env._cache.path:
- metadata = env._cache.path[path].metadata
- elif metadata is None:
- r = finder.find(METADATA_FILENAME)
- # Temporary - for Wheel 0.23 support
- if r is None:
- r = finder.find(WHEEL_METADATA_FILENAME)
- # Temporary - for legacy support
- if r is None:
- r = finder.find(LEGACY_METADATA_FILENAME)
- if r is None:
- raise ValueError('no %s found in %s' % (METADATA_FILENAME, path))
- with contextlib.closing(r.as_stream()) as stream:
- metadata = Metadata(fileobj=stream, scheme='legacy')
-
- super(InstalledDistribution, self).__init__(metadata, path, env)
-
- if env and env._cache_enabled:
- env._cache.add(self)
-
- r = finder.find('REQUESTED')
- self.requested = r is not None
- p = os.path.join(path, 'top_level.txt')
- if os.path.exists(p):
- with open(p, 'rb') as f:
- data = f.read().decode('utf-8')
- self.modules = data.splitlines()
-
- def __repr__(self):
- return '<InstalledDistribution %r %s at %r>' % (self.name, self.version, self.path)
-
- def __str__(self):
- return "%s %s" % (self.name, self.version)
-
- def _get_records(self):
- """
- Get the list of installed files for the distribution
- :return: A list of tuples of path, hash and size. Note that hash and
- size might be ``None`` for some entries. The path is exactly
- as stored in the file (which is as in PEP 376).
- """
- results = []
- r = self.get_distinfo_resource('RECORD')
- with contextlib.closing(r.as_stream()) as stream:
- with CSVReader(stream=stream) as record_reader:
- # Base location is parent dir of .dist-info dir
- # base_location = os.path.dirname(self.path)
- # base_location = os.path.abspath(base_location)
- for row in record_reader:
- missing = [None for i in range(len(row), 3)]
- path, checksum, size = row + missing
- # if not os.path.isabs(path):
- # path = path.replace('/', os.sep)
- # path = os.path.join(base_location, path)
- results.append((path, checksum, size))
- return results
-
- @cached_property
- def exports(self):
- """
- Return the information exported by this distribution.
- :return: A dictionary of exports, mapping an export category to a dict
- of :class:`ExportEntry` instances describing the individual
- export entries, and keyed by name.
- """
- result = {}
- r = self.get_distinfo_resource(EXPORTS_FILENAME)
- if r:
- result = self.read_exports()
- return result
-
- def read_exports(self):
- """
- Read exports data from a file in .ini format.
-
- :return: A dictionary of exports, mapping an export category to a list
- of :class:`ExportEntry` instances describing the individual
- export entries.
- """
- result = {}
- r = self.get_distinfo_resource(EXPORTS_FILENAME)
- if r:
- with contextlib.closing(r.as_stream()) as stream:
- result = read_exports(stream)
- return result
-
- def write_exports(self, exports):
- """
- Write a dictionary of exports to a file in .ini format.
- :param exports: A dictionary of exports, mapping an export category to
- a list of :class:`ExportEntry` instances describing the
- individual export entries.
- """
- rf = self.get_distinfo_file(EXPORTS_FILENAME)
- with open(rf, 'w') as f:
- write_exports(exports, f)
-
- def get_resource_path(self, relative_path):
- """
- NOTE: This API may change in the future.
-
- Return the absolute path to a resource file with the given relative
- path.
-
- :param relative_path: The path, relative to .dist-info, of the resource
- of interest.
- :return: The absolute path where the resource is to be found.
- """
- r = self.get_distinfo_resource('RESOURCES')
- with contextlib.closing(r.as_stream()) as stream:
- with CSVReader(stream=stream) as resources_reader:
- for relative, destination in resources_reader:
- if relative == relative_path:
- return destination
- raise KeyError('no resource file with relative path %r '
- 'is installed' % relative_path)
-
- def list_installed_files(self):
- """
- Iterates over the ``RECORD`` entries and returns a tuple
- ``(path, hash, size)`` for each line.
-
- :returns: iterator of (path, hash, size)
- """
- for result in self._get_records():
- yield result
-
- def write_installed_files(self, paths, prefix, dry_run=False):
- """
- Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any
- existing ``RECORD`` file is silently overwritten.
-
- prefix is used to determine when to write absolute paths.
- """
- prefix = os.path.join(prefix, '')
- base = os.path.dirname(self.path)
- base_under_prefix = base.startswith(prefix)
- base = os.path.join(base, '')
- record_path = self.get_distinfo_file('RECORD')
- logger.info('creating %s', record_path)
- if dry_run:
- return None
- with CSVWriter(record_path) as writer:
- for path in paths:
- if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')):
- # do not put size and hash, as in PEP-376
- hash_value = size = ''
- else:
- size = '%d' % os.path.getsize(path)
- with open(path, 'rb') as fp:
- hash_value = self.get_hash(fp.read())
- if path.startswith(base) or (base_under_prefix and path.startswith(prefix)):
- path = os.path.relpath(path, base)
- writer.writerow((path, hash_value, size))
-
- # add the RECORD file itself
- if record_path.startswith(base):
- record_path = os.path.relpath(record_path, base)
- writer.writerow((record_path, '', ''))
- return record_path
-
- def check_installed_files(self):
- """
- Checks that the hashes and sizes of the files in ``RECORD`` are
- matched by the files themselves. Returns a (possibly empty) list of
- mismatches. Each entry in the mismatch list will be a tuple consisting
- of the path, 'exists', 'size' or 'hash' according to what didn't match
- (existence is checked first, then size, then hash), the expected
- value and the actual value.
- """
- mismatches = []
- base = os.path.dirname(self.path)
- record_path = self.get_distinfo_file('RECORD')
- for path, hash_value, size in self.list_installed_files():
- if not os.path.isabs(path):
- path = os.path.join(base, path)
- if path == record_path:
- continue
- if not os.path.exists(path):
- mismatches.append((path, 'exists', True, False))
- elif os.path.isfile(path):
- actual_size = str(os.path.getsize(path))
- if size and actual_size != size:
- mismatches.append((path, 'size', size, actual_size))
- elif hash_value:
- if '=' in hash_value:
- hasher = hash_value.split('=', 1)[0]
- else:
- hasher = None
-
- with open(path, 'rb') as f:
- actual_hash = self.get_hash(f.read(), hasher)
- if actual_hash != hash_value:
- mismatches.append((path, 'hash', hash_value, actual_hash))
- return mismatches
-
- @cached_property
- def shared_locations(self):
- """
- A dictionary of shared locations whose keys are in the set 'prefix',
- 'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'.
- The corresponding value is the absolute path of that category for
- this distribution, and takes into account any paths selected by the
- user at installation time (e.g. via command-line arguments). In the
- case of the 'namespace' key, this would be a list of absolute paths
- for the roots of namespace packages in this distribution.
-
- The first time this property is accessed, the relevant information is
- read from the SHARED file in the .dist-info directory.
- """
- result = {}
- shared_path = os.path.join(self.path, 'SHARED')
- if os.path.isfile(shared_path):
- with codecs.open(shared_path, 'r', encoding='utf-8') as f:
- lines = f.read().splitlines()
- for line in lines:
- key, value = line.split('=', 1)
- if key == 'namespace':
- result.setdefault(key, []).append(value)
- else:
- result[key] = value
- return result
-
- def write_shared_locations(self, paths, dry_run=False):
- """
- Write shared location information to the SHARED file in .dist-info.
- :param paths: A dictionary as described in the documentation for
- :meth:`shared_locations`.
- :param dry_run: If True, the action is logged but no file is actually
- written.
- :return: The path of the file written to.
- """
- shared_path = os.path.join(self.path, 'SHARED')
- logger.info('creating %s', shared_path)
- if dry_run:
- return None
- lines = []
- for key in ('prefix', 'lib', 'headers', 'scripts', 'data'):
- path = paths[key]
- if os.path.isdir(paths[key]):
- lines.append('%s=%s' % (key, path))
- for ns in paths.get('namespace', ()):
- lines.append('namespace=%s' % ns)
-
- with codecs.open(shared_path, 'w', encoding='utf-8') as f:
- f.write('\n'.join(lines))
- return shared_path
-
- def get_distinfo_resource(self, path):
- if path not in DIST_FILES:
- raise DistlibException('invalid path for a dist-info file: '
- '%r at %r' % (path, self.path))
- finder = resources.finder_for_path(self.path)
- if finder is None:
- raise DistlibException('Unable to get a finder for %s' % self.path)
- return finder.find(path)
-
- def get_distinfo_file(self, path):
- """
- Returns a path located under the ``.dist-info`` directory. Returns a
- string representing the path.
-
- :parameter path: a ``'/'``-separated path relative to the
- ``.dist-info`` directory or an absolute path;
- If *path* is an absolute path and doesn't start
- with the ``.dist-info`` directory path,
- a :class:`DistlibException` is raised
- :type path: str
- :rtype: str
- """
- # Check if it is an absolute path # XXX use relpath, add tests
- if path.find(os.sep) >= 0:
- # it's an absolute path?
- distinfo_dirname, path = path.split(os.sep)[-2:]
- if distinfo_dirname != self.path.split(os.sep)[-1]:
- raise DistlibException('dist-info file %r does not belong to the %r %s '
- 'distribution' % (path, self.name, self.version))
-
- # The file must be relative
- if path not in DIST_FILES:
- raise DistlibException('invalid path for a dist-info file: '
- '%r at %r' % (path, self.path))
-
- return os.path.join(self.path, path)
-
- def list_distinfo_files(self):
- """
- Iterates over the ``RECORD`` entries and returns paths for each line if
- the path is pointing to a file located in the ``.dist-info`` directory
- or one of its subdirectories.
-
- :returns: iterator of paths
- """
- base = os.path.dirname(self.path)
- for path, checksum, size in self._get_records():
- # XXX add separator or use real relpath algo
- if not os.path.isabs(path):
- path = os.path.join(base, path)
- if path.startswith(self.path):
- yield path
-
- def __eq__(self, other):
- return (isinstance(other, InstalledDistribution) and self.path == other.path)
-
- # See http://docs.python.org/reference/datamodel#object.__hash__
- __hash__ = object.__hash__
-
-
-class EggInfoDistribution(BaseInstalledDistribution):
- """Created with the *path* of the ``.egg-info`` directory or file provided
- to the constructor. It reads the metadata contained in the file itself, or
- if the given path happens to be a directory, the metadata is read from the
- file ``PKG-INFO`` under that directory."""
-
- requested = True # as we have no way of knowing, assume it was
- shared_locations = {}
-
- def __init__(self, path, env=None):
-
- def set_name_and_version(s, n, v):
- s.name = n
- s.key = n.lower() # for case-insensitive comparisons
- s.version = v
-
- self.path = path
- self.dist_path = env
- if env and env._cache_enabled and path in env._cache_egg.path:
- metadata = env._cache_egg.path[path].metadata
- set_name_and_version(self, metadata.name, metadata.version)
- else:
- metadata = self._get_metadata(path)
-
- # Need to be set before caching
- set_name_and_version(self, metadata.name, metadata.version)
-
- if env and env._cache_enabled:
- env._cache_egg.add(self)
- super(EggInfoDistribution, self).__init__(metadata, path, env)
-
- def _get_metadata(self, path):
- requires = None
-
- def parse_requires_data(data):
- """Create a list of dependencies from a requires.txt file.
-
- *data*: the contents of a setuptools-produced requires.txt file.
- """
- reqs = []
- lines = data.splitlines()
- for line in lines:
- line = line.strip()
- # sectioned files have bare newlines (separating sections)
- if not line: # pragma: no cover
- continue
- if line.startswith('['): # pragma: no cover
- logger.warning('Unexpected line: quitting requirement scan: %r', line)
- break
- r = parse_requirement(line)
- if not r: # pragma: no cover
- logger.warning('Not recognised as a requirement: %r', line)
- continue
- if r.extras: # pragma: no cover
- logger.warning('extra requirements in requires.txt are '
- 'not supported')
- if not r.constraints:
- reqs.append(r.name)
- else:
- cons = ', '.join('%s%s' % c for c in r.constraints)
- reqs.append('%s (%s)' % (r.name, cons))
- return reqs
-
- def parse_requires_path(req_path):
- """Create a list of dependencies from a requires.txt file.
-
- *req_path*: the path to a setuptools-produced requires.txt file.
- """
-
- reqs = []
- try:
- with codecs.open(req_path, 'r', 'utf-8') as fp:
- reqs = parse_requires_data(fp.read())
- except IOError:
- pass
- return reqs
-
- tl_path = tl_data = None
- if path.endswith('.egg'):
- if os.path.isdir(path):
- p = os.path.join(path, 'EGG-INFO')
- meta_path = os.path.join(p, 'PKG-INFO')
- metadata = Metadata(path=meta_path, scheme='legacy')
- req_path = os.path.join(p, 'requires.txt')
- tl_path = os.path.join(p, 'top_level.txt')
- requires = parse_requires_path(req_path)
- else:
- # FIXME handle the case where zipfile is not available
- zipf = zipimport.zipimporter(path)
- fileobj = StringIO(zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8'))
- metadata = Metadata(fileobj=fileobj, scheme='legacy')
- try:
- data = zipf.get_data('EGG-INFO/requires.txt')
- tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8')
- requires = parse_requires_data(data.decode('utf-8'))
- except IOError:
- requires = None
- elif path.endswith('.egg-info'):
- if os.path.isdir(path):
- req_path = os.path.join(path, 'requires.txt')
- requires = parse_requires_path(req_path)
- path = os.path.join(path, 'PKG-INFO')
- tl_path = os.path.join(path, 'top_level.txt')
- metadata = Metadata(path=path, scheme='legacy')
- else:
- raise DistlibException('path must end with .egg-info or .egg, '
- 'got %r' % path)
-
- if requires:
- metadata.add_requirements(requires)
- # look for top-level modules in top_level.txt, if present
- if tl_data is None:
- if tl_path is not None and os.path.exists(tl_path):
- with open(tl_path, 'rb') as f:
- tl_data = f.read().decode('utf-8')
- if not tl_data:
- tl_data = []
- else:
- tl_data = tl_data.splitlines()
- self.modules = tl_data
- return metadata
-
- def __repr__(self):
- return '<EggInfoDistribution %r %s at %r>' % (self.name, self.version, self.path)
-
- def __str__(self):
- return "%s %s" % (self.name, self.version)
-
- def check_installed_files(self):
- """
- Checks that the hashes and sizes of the files in ``RECORD`` are
- matched by the files themselves. Returns a (possibly empty) list of
- mismatches. Each entry in the mismatch list will be a tuple consisting
- of the path, 'exists', 'size' or 'hash' according to what didn't match
- (existence is checked first, then size, then hash), the expected
- value and the actual value.
- """
- mismatches = []
- record_path = os.path.join(self.path, 'installed-files.txt')
- if os.path.exists(record_path):
- for path, _, _ in self.list_installed_files():
- if path == record_path:
- continue
- if not os.path.exists(path):
- mismatches.append((path, 'exists', True, False))
- return mismatches
-
- def list_installed_files(self):
- """
- Iterates over the ``installed-files.txt`` entries and returns a tuple
- ``(path, hash, size)`` for each line.
-
- :returns: a list of (path, hash, size)
- """
-
- def _md5(path):
- f = open(path, 'rb')
- try:
- content = f.read()
- finally:
- f.close()
- return hashlib.md5(content).hexdigest()
-
- def _size(path):
- return os.stat(path).st_size
-
- record_path = os.path.join(self.path, 'installed-files.txt')
- result = []
- if os.path.exists(record_path):
- with codecs.open(record_path, 'r', encoding='utf-8') as f:
- for line in f:
- line = line.strip()
- p = os.path.normpath(os.path.join(self.path, line))
- # "./" is present as a marker between installed files
- # and installation metadata files
- if not os.path.exists(p):
- logger.warning('Non-existent file: %s', p)
- if p.endswith(('.pyc', '.pyo')):
- continue
- # otherwise fall through and fail
- if not os.path.isdir(p):
- result.append((p, _md5(p), _size(p)))
- result.append((record_path, None, None))
- return result
-
- def list_distinfo_files(self, absolute=False):
- """
- Iterates over the ``installed-files.txt`` entries and returns paths for
- each line if the path is pointing to a file located in the
- ``.egg-info`` directory or one of its subdirectories.
-
- :parameter absolute: If *absolute* is ``True``, each returned path is
- transformed into a local absolute path. Otherwise the
- raw value from ``installed-files.txt`` is returned.
- :type absolute: boolean
- :returns: iterator of paths
- """
- record_path = os.path.join(self.path, 'installed-files.txt')
- if os.path.exists(record_path):
- skip = True
- with codecs.open(record_path, 'r', encoding='utf-8') as f:
- for line in f:
- line = line.strip()
- if line == './':
- skip = False
- continue
- if not skip:
- p = os.path.normpath(os.path.join(self.path, line))
- if p.startswith(self.path):
- if absolute:
- yield p
- else:
- yield line
-
- def __eq__(self, other):
- return (isinstance(other, EggInfoDistribution) and self.path == other.path)
-
- # See http://docs.python.org/reference/datamodel#object.__hash__
- __hash__ = object.__hash__
-
-
-new_dist_class = InstalledDistribution
-old_dist_class = EggInfoDistribution
-
-
-class DependencyGraph(object):
- """
- Represents a dependency graph between distributions.
-
- The dependency relationships are stored in an ``adjacency_list`` that maps
- distributions to a list of ``(other, label)`` tuples where ``other``
- is a distribution and the edge is labeled with ``label`` (i.e. the version
- specifier, if such was provided). Also, for more efficient traversal, for
- every distribution ``x``, a list of predecessors is kept in
- ``reverse_list[x]``. An edge from distribution ``a`` to
- distribution ``b`` means that ``a`` depends on ``b``. If any missing
- dependencies are found, they are stored in ``missing``, which is a
- dictionary that maps distributions to a list of requirements that were not
- provided by any other distributions.
- """
-
- def __init__(self):
- self.adjacency_list = {}
- self.reverse_list = {}
- self.missing = {}
-
- def add_distribution(self, distribution):
- """Add the *distribution* to the graph.
-
- :type distribution: :class:`distutils2.database.InstalledDistribution`
- or :class:`distutils2.database.EggInfoDistribution`
- """
- self.adjacency_list[distribution] = []
- self.reverse_list[distribution] = []
- # self.missing[distribution] = []
-
- def add_edge(self, x, y, label=None):
- """Add an edge from distribution *x* to distribution *y* with the given
- *label*.
-
- :type x: :class:`distutils2.database.InstalledDistribution` or
- :class:`distutils2.database.EggInfoDistribution`
- :type y: :class:`distutils2.database.InstalledDistribution` or
- :class:`distutils2.database.EggInfoDistribution`
- :type label: ``str`` or ``None``
- """
- self.adjacency_list[x].append((y, label))
- # multiple edges are allowed, so be careful
- if x not in self.reverse_list[y]:
- self.reverse_list[y].append(x)
-
- def add_missing(self, distribution, requirement):
- """
- Add a missing *requirement* for the given *distribution*.
-
- :type distribution: :class:`distutils2.database.InstalledDistribution`
- or :class:`distutils2.database.EggInfoDistribution`
- :type requirement: ``str``
- """
- logger.debug('%s missing %r', distribution, requirement)
- self.missing.setdefault(distribution, []).append(requirement)
-
- def _repr_dist(self, dist):
- return '%s %s' % (dist.name, dist.version)
-
- def repr_node(self, dist, level=1):
- """Prints only a subgraph"""
- output = [self._repr_dist(dist)]
- for other, label in self.adjacency_list[dist]:
- dist = self._repr_dist(other)
- if label is not None:
- dist = '%s [%s]' % (dist, label)
- output.append(' ' * level + str(dist))
- suboutput = self.repr_node(other, level + 1)
- subs = suboutput.split('\n')
- output.extend(subs[1:])
- return '\n'.join(output)
-
- def to_dot(self, f, skip_disconnected=True):
- """Writes a DOT output for the graph to the provided file *f*.
-
- If *skip_disconnected* is set to ``True``, then all distributions
- that are not dependent on any other distribution are skipped.
-
- :type f: has to support ``file``-like operations
- :type skip_disconnected: ``bool``
- """
- disconnected = []
-
- f.write("digraph dependencies {\n")
- for dist, adjs in self.adjacency_list.items():
- if len(adjs) == 0 and not skip_disconnected:
- disconnected.append(dist)
- for other, label in adjs:
- if label is not None:
- f.write('"%s" -> "%s" [label="%s"]\n' % (dist.name, other.name, label))
- else:
- f.write('"%s" -> "%s"\n' % (dist.name, other.name))
- if not skip_disconnected and len(disconnected) > 0:
- f.write('subgraph disconnected {\n')
- f.write('label = "Disconnected"\n')
- f.write('bgcolor = red\n')
-
- for dist in disconnected:
- f.write('"%s"' % dist.name)
- f.write('\n')
- f.write('}\n')
- f.write('}\n')
-
- def topological_sort(self):
- """
- Perform a topological sort of the graph.
- :return: A tuple, the first element of which is a topologically sorted
- list of distributions, and the second element of which is a
- list of distributions that cannot be sorted because they have
- circular dependencies and so form a cycle.
- """
- result = []
- # Make a shallow copy of the adjacency list
- alist = {}
- for k, v in self.adjacency_list.items():
- alist[k] = v[:]
- while True:
- # See what we can remove in this run
- to_remove = []
- for k, v in list(alist.items())[:]:
- if not v:
- to_remove.append(k)
- del alist[k]
- if not to_remove:
- # What's left in alist (if anything) is a cycle.
- break
- # Remove from the adjacency list of others
- for k, v in alist.items():
- alist[k] = [(d, r) for d, r in v if d not in to_remove]
- logger.debug('Moving to result: %s', ['%s (%s)' % (d.name, d.version) for d in to_remove])
- result.extend(to_remove)
- return result, list(alist.keys())
-
- def __repr__(self):
- """Representation of the graph"""
- output = []
- for dist, adjs in self.adjacency_list.items():
- output.append(self.repr_node(dist))
- return '\n'.join(output)
-
-
-def make_graph(dists, scheme='default'):
- """Makes a dependency graph from the given distributions.
-
- :parameter dists: a list of distributions
- :type dists: list of :class:`distutils2.database.InstalledDistribution` and
- :class:`distutils2.database.EggInfoDistribution` instances
- :rtype: a :class:`DependencyGraph` instance
- """
- scheme = get_scheme(scheme)
- graph = DependencyGraph()
- provided = {} # maps names to lists of (version, dist) tuples
-
- # first, build the graph and find out what's provided
- for dist in dists:
- graph.add_distribution(dist)
-
- for p in dist.provides:
- name, version = parse_name_and_version(p)
- logger.debug('Add to provided: %s, %s, %s', name, version, dist)
- provided.setdefault(name, []).append((version, dist))
-
- # now make the edges
- for dist in dists:
- requires = (dist.run_requires | dist.meta_requires | dist.build_requires | dist.dev_requires)
- for req in requires:
- try:
- matcher = scheme.matcher(req)
- except UnsupportedVersionError:
- # XXX compat-mode if cannot read the version
- logger.warning('could not read version %r - using name only', req)
- name = req.split()[0]
- matcher = scheme.matcher(name)
-
- name = matcher.key # case-insensitive
-
- matched = False
- if name in provided:
- for version, provider in provided[name]:
- try:
- match = matcher.match(version)
- except UnsupportedVersionError:
- match = False
-
- if match:
- graph.add_edge(dist, provider, req)
- matched = True
- break
- if not matched:
- graph.add_missing(dist, req)
- return graph
-
-
-def get_dependent_dists(dists, dist):
- """Recursively generate a list of distributions from *dists* that are
- dependent on *dist*.
-
- :param dists: a list of distributions
- :param dist: a distribution, member of *dists* for which we are interested
- """
- if dist not in dists:
- raise DistlibException('given distribution %r is not a member '
- 'of the list' % dist.name)
- graph = make_graph(dists)
-
- dep = [dist] # dependent distributions
- todo = graph.reverse_list[dist] # list of nodes we should inspect
-
- while todo:
- d = todo.pop()
- dep.append(d)
- for succ in graph.reverse_list[d]:
- if succ not in dep:
- todo.append(succ)
-
- dep.pop(0) # remove dist from dep, was there to prevent infinite loops
- return dep
-
-
-def get_required_dists(dists, dist):
- """Recursively generate a list of distributions from *dists* that are
- required by *dist*.
-
- :param dists: a list of distributions
- :param dist: a distribution, member of *dists* for which we are interested
- in finding the dependencies.
- """
- if dist not in dists:
- raise DistlibException('given distribution %r is not a member '
- 'of the list' % dist.name)
- graph = make_graph(dists)
-
- req = set() # required distributions
- todo = graph.adjacency_list[dist] # list of nodes we should inspect
- seen = set(t[0] for t in todo) # already added to todo
-
- while todo:
- d = todo.pop()[0]
- req.add(d)
- pred_list = graph.adjacency_list[d]
- for pred in pred_list:
- d = pred[0]
- if d not in req and d not in seen:
- seen.add(d)
- todo.append(pred)
- return req
-
-
-def make_dist(name, version, **kwargs):
- """
- A convenience method for making a dist given just a name and version.
- """
- summary = kwargs.pop('summary', 'Placeholder for summary')
- md = Metadata(**kwargs)
- md.name = name
- md.version = version
- md.summary = summary or 'Placeholder for summary'
- return Distribution(md)
diff --git a/contrib/python/pip/pip/_vendor/distlib/index.py b/contrib/python/pip/pip/_vendor/distlib/index.py
deleted file mode 100644
index 56cd2867145..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/index.py
+++ /dev/null
@@ -1,508 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013-2023 Vinay Sajip.
-# Licensed to the Python Software Foundation under a contributor agreement.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-import hashlib
-import logging
-import os
-import shutil
-import subprocess
-import tempfile
-try:
- from threading import Thread
-except ImportError: # pragma: no cover
- from dummy_threading import Thread
-
-from . import DistlibException
-from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
- urlparse, build_opener, string_types)
-from .util import zip_dir, ServerProxy
-
-logger = logging.getLogger(__name__)
-
-DEFAULT_INDEX = 'https://pypi.org/pypi'
-DEFAULT_REALM = 'pypi'
-
-
-class PackageIndex(object):
- """
- This class represents a package index compatible with PyPI, the Python
- Package Index.
- """
-
- boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$'
-
- def __init__(self, url=None):
- """
- Initialise an instance.
-
- :param url: The URL of the index. If not specified, the URL for PyPI is
- used.
- """
- self.url = url or DEFAULT_INDEX
- self.read_configuration()
- scheme, netloc, path, params, query, frag = urlparse(self.url)
- if params or query or frag or scheme not in ('http', 'https'):
- raise DistlibException('invalid repository: %s' % self.url)
- self.password_handler = None
- self.ssl_verifier = None
- self.gpg = None
- self.gpg_home = None
- with open(os.devnull, 'w') as sink:
- # Use gpg by default rather than gpg2, as gpg2 insists on
- # prompting for passwords
- for s in ('gpg', 'gpg2'):
- try:
- rc = subprocess.check_call([s, '--version'], stdout=sink,
- stderr=sink)
- if rc == 0:
- self.gpg = s
- break
- except OSError:
- pass
-
- def _get_pypirc_command(self):
- """
- Get the distutils command for interacting with PyPI configurations.
- :return: the command.
- """
- from .util import _get_pypirc_command as cmd
- return cmd()
-
- def read_configuration(self):
- """
- Read the PyPI access configuration as supported by distutils. This populates
- ``username``, ``password``, ``realm`` and ``url`` attributes from the
- configuration.
- """
- from .util import _load_pypirc
- cfg = _load_pypirc(self)
- self.username = cfg.get('username')
- self.password = cfg.get('password')
- self.realm = cfg.get('realm', 'pypi')
- self.url = cfg.get('repository', self.url)
-
- def save_configuration(self):
- """
- Save the PyPI access configuration. You must have set ``username`` and
- ``password`` attributes before calling this method.
- """
- self.check_credentials()
- from .util import _store_pypirc
- _store_pypirc(self)
-
- def check_credentials(self):
- """
- Check that ``username`` and ``password`` have been set, and raise an
- exception if not.
- """
- if self.username is None or self.password is None:
- raise DistlibException('username and password must be set')
- pm = HTTPPasswordMgr()
- _, netloc, _, _, _, _ = urlparse(self.url)
- pm.add_password(self.realm, netloc, self.username, self.password)
- self.password_handler = HTTPBasicAuthHandler(pm)
-
- def register(self, metadata): # pragma: no cover
- """
- Register a distribution on PyPI, using the provided metadata.
-
- :param metadata: A :class:`Metadata` instance defining at least a name
- and version number for the distribution to be
- registered.
- :return: The HTTP response received from PyPI upon submission of the
- request.
- """
- self.check_credentials()
- metadata.validate()
- d = metadata.todict()
- d[':action'] = 'verify'
- request = self.encode_request(d.items(), [])
- self.send_request(request)
- d[':action'] = 'submit'
- request = self.encode_request(d.items(), [])
- return self.send_request(request)
-
- def _reader(self, name, stream, outbuf):
- """
- Thread runner for reading lines of from a subprocess into a buffer.
-
- :param name: The logical name of the stream (used for logging only).
- :param stream: The stream to read from. This will typically a pipe
- connected to the output stream of a subprocess.
- :param outbuf: The list to append the read lines to.
- """
- while True:
- s = stream.readline()
- if not s:
- break
- s = s.decode('utf-8').rstrip()
- outbuf.append(s)
- logger.debug('%s: %s' % (name, s))
- stream.close()
-
- def get_sign_command(self, filename, signer, sign_password, keystore=None): # pragma: no cover
- """
- Return a suitable command for signing a file.
-
- :param filename: The pathname to the file to be signed.
- :param signer: The identifier of the signer of the file.
- :param sign_password: The passphrase for the signer's
- private key used for signing.
- :param keystore: The path to a directory which contains the keys
- used in verification. If not specified, the
- instance's ``gpg_home`` attribute is used instead.
- :return: The signing command as a list suitable to be
- passed to :class:`subprocess.Popen`.
- """
- cmd = [self.gpg, '--status-fd', '2', '--no-tty']
- if keystore is None:
- keystore = self.gpg_home
- if keystore:
- cmd.extend(['--homedir', keystore])
- if sign_password is not None:
- cmd.extend(['--batch', '--passphrase-fd', '0'])
- td = tempfile.mkdtemp()
- sf = os.path.join(td, os.path.basename(filename) + '.asc')
- cmd.extend(['--detach-sign', '--armor', '--local-user',
- signer, '--output', sf, filename])
- logger.debug('invoking: %s', ' '.join(cmd))
- return cmd, sf
-
- def run_command(self, cmd, input_data=None):
- """
- Run a command in a child process , passing it any input data specified.
-
- :param cmd: The command to run.
- :param input_data: If specified, this must be a byte string containing
- data to be sent to the child process.
- :return: A tuple consisting of the subprocess' exit code, a list of
- lines read from the subprocess' ``stdout``, and a list of
- lines read from the subprocess' ``stderr``.
- """
- kwargs = {
- 'stdout': subprocess.PIPE,
- 'stderr': subprocess.PIPE,
- }
- if input_data is not None:
- kwargs['stdin'] = subprocess.PIPE
- stdout = []
- stderr = []
- p = subprocess.Popen(cmd, **kwargs)
- # We don't use communicate() here because we may need to
- # get clever with interacting with the command
- t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout))
- t1.start()
- t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr))
- t2.start()
- if input_data is not None:
- p.stdin.write(input_data)
- p.stdin.close()
-
- p.wait()
- t1.join()
- t2.join()
- return p.returncode, stdout, stderr
-
- def sign_file(self, filename, signer, sign_password, keystore=None): # pragma: no cover
- """
- Sign a file.
-
- :param filename: The pathname to the file to be signed.
- :param signer: The identifier of the signer of the file.
- :param sign_password: The passphrase for the signer's
- private key used for signing.
- :param keystore: The path to a directory which contains the keys
- used in signing. If not specified, the instance's
- ``gpg_home`` attribute is used instead.
- :return: The absolute pathname of the file where the signature is
- stored.
- """
- cmd, sig_file = self.get_sign_command(filename, signer, sign_password,
- keystore)
- rc, stdout, stderr = self.run_command(cmd,
- sign_password.encode('utf-8'))
- if rc != 0:
- raise DistlibException('sign command failed with error '
- 'code %s' % rc)
- return sig_file
-
- def upload_file(self, metadata, filename, signer=None, sign_password=None,
- filetype='sdist', pyversion='source', keystore=None):
- """
- Upload a release file to the index.
-
- :param metadata: A :class:`Metadata` instance defining at least a name
- and version number for the file to be uploaded.
- :param filename: The pathname of the file to be uploaded.
- :param signer: The identifier of the signer of the file.
- :param sign_password: The passphrase for the signer's
- private key used for signing.
- :param filetype: The type of the file being uploaded. This is the
- distutils command which produced that file, e.g.
- ``sdist`` or ``bdist_wheel``.
- :param pyversion: The version of Python which the release relates
- to. For code compatible with any Python, this would
- be ``source``, otherwise it would be e.g. ``3.2``.
- :param keystore: The path to a directory which contains the keys
- used in signing. If not specified, the instance's
- ``gpg_home`` attribute is used instead.
- :return: The HTTP response received from PyPI upon submission of the
- request.
- """
- self.check_credentials()
- if not os.path.exists(filename):
- raise DistlibException('not found: %s' % filename)
- metadata.validate()
- d = metadata.todict()
- sig_file = None
- if signer:
- if not self.gpg:
- logger.warning('no signing program available - not signed')
- else:
- sig_file = self.sign_file(filename, signer, sign_password,
- keystore)
- with open(filename, 'rb') as f:
- file_data = f.read()
- md5_digest = hashlib.md5(file_data).hexdigest()
- sha256_digest = hashlib.sha256(file_data).hexdigest()
- d.update({
- ':action': 'file_upload',
- 'protocol_version': '1',
- 'filetype': filetype,
- 'pyversion': pyversion,
- 'md5_digest': md5_digest,
- 'sha256_digest': sha256_digest,
- })
- files = [('content', os.path.basename(filename), file_data)]
- if sig_file:
- with open(sig_file, 'rb') as f:
- sig_data = f.read()
- files.append(('gpg_signature', os.path.basename(sig_file),
- sig_data))
- shutil.rmtree(os.path.dirname(sig_file))
- request = self.encode_request(d.items(), files)
- return self.send_request(request)
-
- def upload_documentation(self, metadata, doc_dir): # pragma: no cover
- """
- Upload documentation to the index.
-
- :param metadata: A :class:`Metadata` instance defining at least a name
- and version number for the documentation to be
- uploaded.
- :param doc_dir: The pathname of the directory which contains the
- documentation. This should be the directory that
- contains the ``index.html`` for the documentation.
- :return: The HTTP response received from PyPI upon submission of the
- request.
- """
- self.check_credentials()
- if not os.path.isdir(doc_dir):
- raise DistlibException('not a directory: %r' % doc_dir)
- fn = os.path.join(doc_dir, 'index.html')
- if not os.path.exists(fn):
- raise DistlibException('not found: %r' % fn)
- metadata.validate()
- name, version = metadata.name, metadata.version
- zip_data = zip_dir(doc_dir).getvalue()
- fields = [(':action', 'doc_upload'),
- ('name', name), ('version', version)]
- files = [('content', name, zip_data)]
- request = self.encode_request(fields, files)
- return self.send_request(request)
-
- def get_verify_command(self, signature_filename, data_filename,
- keystore=None):
- """
- Return a suitable command for verifying a file.
-
- :param signature_filename: The pathname to the file containing the
- signature.
- :param data_filename: The pathname to the file containing the
- signed data.
- :param keystore: The path to a directory which contains the keys
- used in verification. If not specified, the
- instance's ``gpg_home`` attribute is used instead.
- :return: The verifying command as a list suitable to be
- passed to :class:`subprocess.Popen`.
- """
- cmd = [self.gpg, '--status-fd', '2', '--no-tty']
- if keystore is None:
- keystore = self.gpg_home
- if keystore:
- cmd.extend(['--homedir', keystore])
- cmd.extend(['--verify', signature_filename, data_filename])
- logger.debug('invoking: %s', ' '.join(cmd))
- return cmd
-
- def verify_signature(self, signature_filename, data_filename,
- keystore=None):
- """
- Verify a signature for a file.
-
- :param signature_filename: The pathname to the file containing the
- signature.
- :param data_filename: The pathname to the file containing the
- signed data.
- :param keystore: The path to a directory which contains the keys
- used in verification. If not specified, the
- instance's ``gpg_home`` attribute is used instead.
- :return: True if the signature was verified, else False.
- """
- if not self.gpg:
- raise DistlibException('verification unavailable because gpg '
- 'unavailable')
- cmd = self.get_verify_command(signature_filename, data_filename,
- keystore)
- rc, stdout, stderr = self.run_command(cmd)
- if rc not in (0, 1):
- raise DistlibException('verify command failed with error code %s' % rc)
- return rc == 0
-
- def download_file(self, url, destfile, digest=None, reporthook=None):
- """
- This is a convenience method for downloading a file from an URL.
- Normally, this will be a file from the index, though currently
- no check is made for this (i.e. a file can be downloaded from
- anywhere).
-
- The method is just like the :func:`urlretrieve` function in the
- standard library, except that it allows digest computation to be
- done during download and checking that the downloaded data
- matched any expected value.
-
- :param url: The URL of the file to be downloaded (assumed to be
- available via an HTTP GET request).
- :param destfile: The pathname where the downloaded file is to be
- saved.
- :param digest: If specified, this must be a (hasher, value)
- tuple, where hasher is the algorithm used (e.g.
- ``'md5'``) and ``value`` is the expected value.
- :param reporthook: The same as for :func:`urlretrieve` in the
- standard library.
- """
- if digest is None:
- digester = None
- logger.debug('No digest specified')
- else:
- if isinstance(digest, (list, tuple)):
- hasher, digest = digest
- else:
- hasher = 'md5'
- digester = getattr(hashlib, hasher)()
- logger.debug('Digest specified: %s' % digest)
- # The following code is equivalent to urlretrieve.
- # We need to do it this way so that we can compute the
- # digest of the file as we go.
- with open(destfile, 'wb') as dfp:
- # addinfourl is not a context manager on 2.x
- # so we have to use try/finally
- sfp = self.send_request(Request(url))
- try:
- headers = sfp.info()
- blocksize = 8192
- size = -1
- read = 0
- blocknum = 0
- if "content-length" in headers:
- size = int(headers["Content-Length"])
- if reporthook:
- reporthook(blocknum, blocksize, size)
- while True:
- block = sfp.read(blocksize)
- if not block:
- break
- read += len(block)
- dfp.write(block)
- if digester:
- digester.update(block)
- blocknum += 1
- if reporthook:
- reporthook(blocknum, blocksize, size)
- finally:
- sfp.close()
-
- # check that we got the whole file, if we can
- if size >= 0 and read < size:
- raise DistlibException(
- 'retrieval incomplete: got only %d out of %d bytes'
- % (read, size))
- # if we have a digest, it must match.
- if digester:
- actual = digester.hexdigest()
- if digest != actual:
- raise DistlibException('%s digest mismatch for %s: expected '
- '%s, got %s' % (hasher, destfile,
- digest, actual))
- logger.debug('Digest verified: %s', digest)
-
- def send_request(self, req):
- """
- Send a standard library :class:`Request` to PyPI and return its
- response.
-
- :param req: The request to send.
- :return: The HTTP response from PyPI (a standard library HTTPResponse).
- """
- handlers = []
- if self.password_handler:
- handlers.append(self.password_handler)
- if self.ssl_verifier:
- handlers.append(self.ssl_verifier)
- opener = build_opener(*handlers)
- return opener.open(req)
-
- def encode_request(self, fields, files):
- """
- Encode fields and files for posting to an HTTP server.
-
- :param fields: The fields to send as a list of (fieldname, value)
- tuples.
- :param files: The files to send as a list of (fieldname, filename,
- file_bytes) tuple.
- """
- # Adapted from packaging, which in turn was adapted from
- # http://code.activestate.com/recipes/146306
-
- parts = []
- boundary = self.boundary
- for k, values in fields:
- if not isinstance(values, (list, tuple)):
- values = [values]
-
- for v in values:
- parts.extend((
- b'--' + boundary,
- ('Content-Disposition: form-data; name="%s"' %
- k).encode('utf-8'),
- b'',
- v.encode('utf-8')))
- for key, filename, value in files:
- parts.extend((
- b'--' + boundary,
- ('Content-Disposition: form-data; name="%s"; filename="%s"' %
- (key, filename)).encode('utf-8'),
- b'',
- value))
-
- parts.extend((b'--' + boundary + b'--', b''))
-
- body = b'\r\n'.join(parts)
- ct = b'multipart/form-data; boundary=' + boundary
- headers = {
- 'Content-type': ct,
- 'Content-length': str(len(body))
- }
- return Request(self.url, body, headers)
-
- def search(self, terms, operator=None): # pragma: no cover
- if isinstance(terms, string_types):
- terms = {'name': terms}
- rpc_proxy = ServerProxy(self.url, timeout=3.0)
- try:
- return rpc_proxy.search(terms, operator or 'and')
- finally:
- rpc_proxy('close')()
diff --git a/contrib/python/pip/pip/_vendor/distlib/locators.py b/contrib/python/pip/pip/_vendor/distlib/locators.py
deleted file mode 100644
index 222c1bf3e90..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/locators.py
+++ /dev/null
@@ -1,1295 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012-2023 Vinay Sajip.
-# Licensed to the Python Software Foundation under a contributor agreement.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-
-import gzip
-from io import BytesIO
-import json
-import logging
-import os
-import posixpath
-import re
-try:
- import threading
-except ImportError: # pragma: no cover
- import dummy_threading as threading
-import zlib
-
-from . import DistlibException
-from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, queue, quote, unescape, build_opener,
- HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError)
-from .database import Distribution, DistributionPath, make_dist
-from .metadata import Metadata, MetadataInvalidError
-from .util import (cached_property, ensure_slash, split_filename, get_project_data, parse_requirement,
- parse_name_and_version, ServerProxy, normalize_name)
-from .version import get_scheme, UnsupportedVersionError
-from .wheel import Wheel, is_compatible
-
-logger = logging.getLogger(__name__)
-
-HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)')
-CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
-HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
-DEFAULT_INDEX = 'https://pypi.org/pypi'
-
-
-def get_all_distribution_names(url=None):
- """
- Return all distribution names known by an index.
- :param url: The URL of the index.
- :return: A list of all known distribution names.
- """
- if url is None:
- url = DEFAULT_INDEX
- client = ServerProxy(url, timeout=3.0)
- try:
- return client.list_packages()
- finally:
- client('close')()
-
-
-class RedirectHandler(BaseRedirectHandler):
- """
- A class to work around a bug in some Python 3.2.x releases.
- """
-
- # There's a bug in the base version for some 3.2.x
- # (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header
- # returns e.g. /abc, it bails because it says the scheme ''
- # is bogus, when actually it should use the request's
- # URL for the scheme. See Python issue #13696.
- def http_error_302(self, req, fp, code, msg, headers):
- # Some servers (incorrectly) return multiple Location headers
- # (so probably same goes for URI). Use first header.
- newurl = None
- for key in ('location', 'uri'):
- if key in headers:
- newurl = headers[key]
- break
- if newurl is None: # pragma: no cover
- return
- urlparts = urlparse(newurl)
- if urlparts.scheme == '':
- newurl = urljoin(req.get_full_url(), newurl)
- if hasattr(headers, 'replace_header'):
- headers.replace_header(key, newurl)
- else:
- headers[key] = newurl
- return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
-
- http_error_301 = http_error_303 = http_error_307 = http_error_302
-
-
-class Locator(object):
- """
- A base class for locators - things that locate distributions.
- """
- source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz')
- binary_extensions = ('.egg', '.exe', '.whl')
- excluded_extensions = ('.pdf', )
-
- # A list of tags indicating which wheels you want to match. The default
- # value of None matches against the tags compatible with the running
- # Python. If you want to match other values, set wheel_tags on a locator
- # instance to a list of tuples (pyver, abi, arch) which you want to match.
- wheel_tags = None
-
- downloadable_extensions = source_extensions + ('.whl', )
-
- def __init__(self, scheme='default'):
- """
- Initialise an instance.
- :param scheme: Because locators look for most recent versions, they
- need to know the version scheme to use. This specifies
- the current PEP-recommended scheme - use ``'legacy'``
- if you need to support existing distributions on PyPI.
- """
- self._cache = {}
- self.scheme = scheme
- # Because of bugs in some of the handlers on some of the platforms,
- # we use our own opener rather than just using urlopen.
- self.opener = build_opener(RedirectHandler())
- # If get_project() is called from locate(), the matcher instance
- # is set from the requirement passed to locate(). See issue #18 for
- # why this can be useful to know.
- self.matcher = None
- self.errors = queue.Queue()
-
- def get_errors(self):
- """
- Return any errors which have occurred.
- """
- result = []
- while not self.errors.empty(): # pragma: no cover
- try:
- e = self.errors.get(False)
- result.append(e)
- except self.errors.Empty:
- continue
- self.errors.task_done()
- return result
-
- def clear_errors(self):
- """
- Clear any errors which may have been logged.
- """
- # Just get the errors and throw them away
- self.get_errors()
-
- def clear_cache(self):
- self._cache.clear()
-
- def _get_scheme(self):
- return self._scheme
-
- def _set_scheme(self, value):
- self._scheme = value
-
- scheme = property(_get_scheme, _set_scheme)
-
- def _get_project(self, name):
- """
- For a given project, get a dictionary mapping available versions to Distribution
- instances.
-
- This should be implemented in subclasses.
-
- If called from a locate() request, self.matcher will be set to a
- matcher for the requirement to satisfy, otherwise it will be None.
- """
- raise NotImplementedError('Please implement in the subclass')
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- raise NotImplementedError('Please implement in the subclass')
-
- def get_project(self, name):
- """
- For a given project, get a dictionary mapping available versions to Distribution
- instances.
-
- This calls _get_project to do all the work, and just implements a caching layer on top.
- """
- if self._cache is None: # pragma: no cover
- result = self._get_project(name)
- elif name in self._cache:
- result = self._cache[name]
- else:
- self.clear_errors()
- result = self._get_project(name)
- self._cache[name] = result
- return result
-
- def score_url(self, url):
- """
- Give an url a score which can be used to choose preferred URLs
- for a given project release.
- """
- t = urlparse(url)
- basename = posixpath.basename(t.path)
- compatible = True
- is_wheel = basename.endswith('.whl')
- is_downloadable = basename.endswith(self.downloadable_extensions)
- if is_wheel:
- compatible = is_compatible(Wheel(basename), self.wheel_tags)
- return (t.scheme == 'https', 'pypi.org' in t.netloc, is_downloadable, is_wheel, compatible, basename)
-
- def prefer_url(self, url1, url2):
- """
- Choose one of two URLs where both are candidates for distribution
- archives for the same version of a distribution (for example,
- .tar.gz vs. zip).
-
- The current implementation favours https:// URLs over http://, archives
- from PyPI over those from other locations, wheel compatibility (if a
- wheel) and then the archive name.
- """
- result = url2
- if url1:
- s1 = self.score_url(url1)
- s2 = self.score_url(url2)
- if s1 > s2:
- result = url1
- if result != url2:
- logger.debug('Not replacing %r with %r', url1, url2)
- else:
- logger.debug('Replacing %r with %r', url1, url2)
- return result
-
- def split_filename(self, filename, project_name):
- """
- Attempt to split a filename in project name, version and Python version.
- """
- return split_filename(filename, project_name)
-
- def convert_url_to_download_info(self, url, project_name):
- """
- See if a URL is a candidate for a download URL for a project (the URL
- has typically been scraped from an HTML page).
-
- If it is, a dictionary is returned with keys "name", "version",
- "filename" and "url"; otherwise, None is returned.
- """
-
- def same_project(name1, name2):
- return normalize_name(name1) == normalize_name(name2)
-
- result = None
- scheme, netloc, path, params, query, frag = urlparse(url)
- if frag.lower().startswith('egg='): # pragma: no cover
- logger.debug('%s: version hint in fragment: %r', project_name, frag)
- m = HASHER_HASH.match(frag)
- if m:
- algo, digest = m.groups()
- else:
- algo, digest = None, None
- origpath = path
- if path and path[-1] == '/': # pragma: no cover
- path = path[:-1]
- if path.endswith('.whl'):
- try:
- wheel = Wheel(path)
- if not is_compatible(wheel, self.wheel_tags):
- logger.debug('Wheel not compatible: %s', path)
- else:
- if project_name is None:
- include = True
- else:
- include = same_project(wheel.name, project_name)
- if include:
- result = {
- 'name': wheel.name,
- 'version': wheel.version,
- 'filename': wheel.filename,
- 'url': urlunparse((scheme, netloc, origpath, params, query, '')),
- 'python-version': ', '.join(['.'.join(list(v[2:])) for v in wheel.pyver]),
- }
- except Exception: # pragma: no cover
- logger.warning('invalid path for wheel: %s', path)
- elif not path.endswith(self.downloadable_extensions): # pragma: no cover
- logger.debug('Not downloadable: %s', path)
- else: # downloadable extension
- path = filename = posixpath.basename(path)
- for ext in self.downloadable_extensions:
- if path.endswith(ext):
- path = path[:-len(ext)]
- t = self.split_filename(path, project_name)
- if not t: # pragma: no cover
- logger.debug('No match for project/version: %s', path)
- else:
- name, version, pyver = t
- if not project_name or same_project(project_name, name):
- result = {
- 'name': name,
- 'version': version,
- 'filename': filename,
- 'url': urlunparse((scheme, netloc, origpath, params, query, '')),
- }
- if pyver: # pragma: no cover
- result['python-version'] = pyver
- break
- if result and algo:
- result['%s_digest' % algo] = digest
- return result
-
- def _get_digest(self, info):
- """
- Get a digest from a dictionary by looking at a "digests" dictionary
- or keys of the form 'algo_digest'.
-
- Returns a 2-tuple (algo, digest) if found, else None. Currently
- looks only for SHA256, then MD5.
- """
- result = None
- if 'digests' in info:
- digests = info['digests']
- for algo in ('sha256', 'md5'):
- if algo in digests:
- result = (algo, digests[algo])
- break
- if not result:
- for algo in ('sha256', 'md5'):
- key = '%s_digest' % algo
- if key in info:
- result = (algo, info[key])
- break
- return result
-
- def _update_version_data(self, result, info):
- """
- Update a result dictionary (the final result from _get_project) with a
- dictionary for a specific version, which typically holds information
- gleaned from a filename or URL for an archive for the distribution.
- """
- name = info.pop('name')
- version = info.pop('version')
- if version in result:
- dist = result[version]
- md = dist.metadata
- else:
- dist = make_dist(name, version, scheme=self.scheme)
- md = dist.metadata
- dist.digest = digest = self._get_digest(info)
- url = info['url']
- result['digests'][url] = digest
- if md.source_url != info['url']:
- md.source_url = self.prefer_url(md.source_url, url)
- result['urls'].setdefault(version, set()).add(url)
- dist.locator = self
- result[version] = dist
-
- def locate(self, requirement, prereleases=False):
- """
- Find the most recent distribution which matches the given
- requirement.
-
- :param requirement: A requirement of the form 'foo (1.0)' or perhaps
- 'foo (>= 1.0, < 2.0, != 1.3)'
- :param prereleases: If ``True``, allow pre-release versions
- to be located. Otherwise, pre-release versions
- are not returned.
- :return: A :class:`Distribution` instance, or ``None`` if no such
- distribution could be located.
- """
- result = None
- r = parse_requirement(requirement)
- if r is None: # pragma: no cover
- raise DistlibException('Not a valid requirement: %r' % requirement)
- scheme = get_scheme(self.scheme)
- self.matcher = matcher = scheme.matcher(r.requirement)
- logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__)
- versions = self.get_project(r.name)
- if len(versions) > 2: # urls and digests keys are present
- # sometimes, versions are invalid
- slist = []
- vcls = matcher.version_class
- for k in versions:
- if k in ('urls', 'digests'):
- continue
- try:
- if not matcher.match(k):
- pass # logger.debug('%s did not match %r', matcher, k)
- else:
- if prereleases or not vcls(k).is_prerelease:
- slist.append(k)
- except Exception: # pragma: no cover
- logger.warning('error matching %s with %r', matcher, k)
- pass # slist.append(k)
- if len(slist) > 1:
- slist = sorted(slist, key=scheme.key)
- if slist:
- logger.debug('sorted list: %s', slist)
- version = slist[-1]
- result = versions[version]
- if result:
- if r.extras:
- result.extras = r.extras
- result.download_urls = versions.get('urls', {}).get(version, set())
- d = {}
- sd = versions.get('digests', {})
- for url in result.download_urls:
- if url in sd: # pragma: no cover
- d[url] = sd[url]
- result.digests = d
- self.matcher = None
- return result
-
-
-class PyPIRPCLocator(Locator):
- """
- This locator uses XML-RPC to locate distributions. It therefore
- cannot be used with simple mirrors (that only mirror file content).
- """
-
- def __init__(self, url, **kwargs):
- """
- Initialise an instance.
-
- :param url: The URL to use for XML-RPC.
- :param kwargs: Passed to the superclass constructor.
- """
- super(PyPIRPCLocator, self).__init__(**kwargs)
- self.base_url = url
- self.client = ServerProxy(url, timeout=3.0)
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- return set(self.client.list_packages())
-
- def _get_project(self, name):
- result = {'urls': {}, 'digests': {}}
- versions = self.client.package_releases(name, True)
- for v in versions:
- urls = self.client.release_urls(name, v)
- data = self.client.release_data(name, v)
- metadata = Metadata(scheme=self.scheme)
- metadata.name = data['name']
- metadata.version = data['version']
- metadata.license = data.get('license')
- metadata.keywords = data.get('keywords', [])
- metadata.summary = data.get('summary')
- dist = Distribution(metadata)
- if urls:
- info = urls[0]
- metadata.source_url = info['url']
- dist.digest = self._get_digest(info)
- dist.locator = self
- result[v] = dist
- for info in urls:
- url = info['url']
- digest = self._get_digest(info)
- result['urls'].setdefault(v, set()).add(url)
- result['digests'][url] = digest
- return result
-
-
-class PyPIJSONLocator(Locator):
- """
- This locator uses PyPI's JSON interface. It's very limited in functionality
- and probably not worth using.
- """
-
- def __init__(self, url, **kwargs):
- super(PyPIJSONLocator, self).__init__(**kwargs)
- self.base_url = ensure_slash(url)
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- raise NotImplementedError('Not available from this locator')
-
- def _get_project(self, name):
- result = {'urls': {}, 'digests': {}}
- url = urljoin(self.base_url, '%s/json' % quote(name))
- try:
- resp = self.opener.open(url)
- data = resp.read().decode() # for now
- d = json.loads(data)
- md = Metadata(scheme=self.scheme)
- data = d['info']
- md.name = data['name']
- md.version = data['version']
- md.license = data.get('license')
- md.keywords = data.get('keywords', [])
- md.summary = data.get('summary')
- dist = Distribution(md)
- dist.locator = self
- # urls = d['urls']
- result[md.version] = dist
- for info in d['urls']:
- url = info['url']
- dist.download_urls.add(url)
- dist.digests[url] = self._get_digest(info)
- result['urls'].setdefault(md.version, set()).add(url)
- result['digests'][url] = self._get_digest(info)
- # Now get other releases
- for version, infos in d['releases'].items():
- if version == md.version:
- continue # already done
- omd = Metadata(scheme=self.scheme)
- omd.name = md.name
- omd.version = version
- odist = Distribution(omd)
- odist.locator = self
- result[version] = odist
- for info in infos:
- url = info['url']
- odist.download_urls.add(url)
- odist.digests[url] = self._get_digest(info)
- result['urls'].setdefault(version, set()).add(url)
- result['digests'][url] = self._get_digest(info)
-
-
-# for info in urls:
-# md.source_url = info['url']
-# dist.digest = self._get_digest(info)
-# dist.locator = self
-# for info in urls:
-# url = info['url']
-# result['urls'].setdefault(md.version, set()).add(url)
-# result['digests'][url] = self._get_digest(info)
- except Exception as e:
- self.errors.put(text_type(e))
- logger.exception('JSON fetch failed: %s', e)
- return result
-
-
-class Page(object):
- """
- This class represents a scraped HTML page.
- """
- # The following slightly hairy-looking regex just looks for the contents of
- # an anchor link, which has an attribute "href" either immediately preceded
- # or immediately followed by a "rel" attribute. The attribute values can be
- # declared with double quotes, single quotes or no quotes - which leads to
- # the length of the expression.
- _href = re.compile(
- """
-(rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
-href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
-(\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
-""", re.I | re.S | re.X)
- _base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I | re.S)
-
- def __init__(self, data, url):
- """
- Initialise an instance with the Unicode page contents and the URL they
- came from.
- """
- self.data = data
- self.base_url = self.url = url
- m = self._base.search(self.data)
- if m:
- self.base_url = m.group(1)
-
- _clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
-
- @cached_property
- def links(self):
- """
- Return the URLs of all the links on a page together with information
- about their "rel" attribute, for determining which ones to treat as
- downloads and which ones to queue for further scraping.
- """
-
- def clean(url):
- "Tidy up an URL."
- scheme, netloc, path, params, query, frag = urlparse(url)
- return urlunparse((scheme, netloc, quote(path), params, query, frag))
-
- result = set()
- for match in self._href.finditer(self.data):
- d = match.groupdict('')
- rel = (d['rel1'] or d['rel2'] or d['rel3'] or d['rel4'] or d['rel5'] or d['rel6'])
- url = d['url1'] or d['url2'] or d['url3']
- url = urljoin(self.base_url, url)
- url = unescape(url)
- url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url)
- result.add((url, rel))
- # We sort the result, hoping to bring the most recent versions
- # to the front
- result = sorted(result, key=lambda t: t[0], reverse=True)
- return result
-
-
-class SimpleScrapingLocator(Locator):
- """
- A locator which scrapes HTML pages to locate downloads for a distribution.
- This runs multiple threads to do the I/O; performance is at least as good
- as pip's PackageFinder, which works in an analogous fashion.
- """
-
- # These are used to deal with various Content-Encoding schemes.
- decoders = {
- 'deflate': zlib.decompress,
- 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(),
- 'none': lambda b: b,
- }
-
- def __init__(self, url, timeout=None, num_workers=10, **kwargs):
- """
- Initialise an instance.
- :param url: The root URL to use for scraping.
- :param timeout: The timeout, in seconds, to be applied to requests.
- This defaults to ``None`` (no timeout specified).
- :param num_workers: The number of worker threads you want to do I/O,
- This defaults to 10.
- :param kwargs: Passed to the superclass.
- """
- super(SimpleScrapingLocator, self).__init__(**kwargs)
- self.base_url = ensure_slash(url)
- self.timeout = timeout
- self._page_cache = {}
- self._seen = set()
- self._to_fetch = queue.Queue()
- self._bad_hosts = set()
- self.skip_externals = False
- self.num_workers = num_workers
- self._lock = threading.RLock()
- # See issue #45: we need to be resilient when the locator is used
- # in a thread, e.g. with concurrent.futures. We can't use self._lock
- # as it is for coordinating our internal threads - the ones created
- # in _prepare_threads.
- self._gplock = threading.RLock()
- self.platform_check = False # See issue #112
-
- def _prepare_threads(self):
- """
- Threads are created only when get_project is called, and terminate
- before it returns. They are there primarily to parallelise I/O (i.e.
- fetching web pages).
- """
- self._threads = []
- for i in range(self.num_workers):
- t = threading.Thread(target=self._fetch)
- t.daemon = True
- t.start()
- self._threads.append(t)
-
- def _wait_threads(self):
- """
- Tell all the threads to terminate (by sending a sentinel value) and
- wait for them to do so.
- """
- # Note that you need two loops, since you can't say which
- # thread will get each sentinel
- for t in self._threads:
- self._to_fetch.put(None) # sentinel
- for t in self._threads:
- t.join()
- self._threads = []
-
- def _get_project(self, name):
- result = {'urls': {}, 'digests': {}}
- with self._gplock:
- self.result = result
- self.project_name = name
- url = urljoin(self.base_url, '%s/' % quote(name))
- self._seen.clear()
- self._page_cache.clear()
- self._prepare_threads()
- try:
- logger.debug('Queueing %s', url)
- self._to_fetch.put(url)
- self._to_fetch.join()
- finally:
- self._wait_threads()
- del self.result
- return result
-
- platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|'
- r'win(32|_amd64)|macosx_?\d+)\b', re.I)
-
- def _is_platform_dependent(self, url):
- """
- Does an URL refer to a platform-specific download?
- """
- return self.platform_dependent.search(url)
-
- def _process_download(self, url):
- """
- See if an URL is a suitable download for a project.
-
- If it is, register information in the result dictionary (for
- _get_project) about the specific version it's for.
-
- Note that the return value isn't actually used other than as a boolean
- value.
- """
- if self.platform_check and self._is_platform_dependent(url):
- info = None
- else:
- info = self.convert_url_to_download_info(url, self.project_name)
- logger.debug('process_download: %s -> %s', url, info)
- if info:
- with self._lock: # needed because self.result is shared
- self._update_version_data(self.result, info)
- return info
-
- def _should_queue(self, link, referrer, rel):
- """
- Determine whether a link URL from a referring page and with a
- particular "rel" attribute should be queued for scraping.
- """
- scheme, netloc, path, _, _, _ = urlparse(link)
- if path.endswith(self.source_extensions + self.binary_extensions + self.excluded_extensions):
- result = False
- elif self.skip_externals and not link.startswith(self.base_url):
- result = False
- elif not referrer.startswith(self.base_url):
- result = False
- elif rel not in ('homepage', 'download'):
- result = False
- elif scheme not in ('http', 'https', 'ftp'):
- result = False
- elif self._is_platform_dependent(link):
- result = False
- else:
- host = netloc.split(':', 1)[0]
- if host.lower() == 'localhost':
- result = False
- else:
- result = True
- logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, referrer, result)
- return result
-
- def _fetch(self):
- """
- Get a URL to fetch from the work queue, get the HTML page, examine its
- links for download candidates and candidates for further scraping.
-
- This is a handy method to run in a thread.
- """
- while True:
- url = self._to_fetch.get()
- try:
- if url:
- page = self.get_page(url)
- if page is None: # e.g. after an error
- continue
- for link, rel in page.links:
- if link not in self._seen:
- try:
- self._seen.add(link)
- if (not self._process_download(link) and self._should_queue(link, url, rel)):
- logger.debug('Queueing %s from %s', link, url)
- self._to_fetch.put(link)
- except MetadataInvalidError: # e.g. invalid versions
- pass
- except Exception as e: # pragma: no cover
- self.errors.put(text_type(e))
- finally:
- # always do this, to avoid hangs :-)
- self._to_fetch.task_done()
- if not url:
- # logger.debug('Sentinel seen, quitting.')
- break
-
- def get_page(self, url):
- """
- Get the HTML for an URL, possibly from an in-memory cache.
-
- XXX TODO Note: this cache is never actually cleared. It's assumed that
- the data won't get stale over the lifetime of a locator instance (not
- necessarily true for the default_locator).
- """
- # http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api
- scheme, netloc, path, _, _, _ = urlparse(url)
- if scheme == 'file' and os.path.isdir(url2pathname(path)):
- url = urljoin(ensure_slash(url), 'index.html')
-
- if url in self._page_cache:
- result = self._page_cache[url]
- logger.debug('Returning %s from cache: %s', url, result)
- else:
- host = netloc.split(':', 1)[0]
- result = None
- if host in self._bad_hosts:
- logger.debug('Skipping %s due to bad host %s', url, host)
- else:
- req = Request(url, headers={'Accept-encoding': 'identity'})
- try:
- logger.debug('Fetching %s', url)
- resp = self.opener.open(req, timeout=self.timeout)
- logger.debug('Fetched %s', url)
- headers = resp.info()
- content_type = headers.get('Content-Type', '')
- if HTML_CONTENT_TYPE.match(content_type):
- final_url = resp.geturl()
- data = resp.read()
- encoding = headers.get('Content-Encoding')
- if encoding:
- decoder = self.decoders[encoding] # fail if not found
- data = decoder(data)
- encoding = 'utf-8'
- m = CHARSET.search(content_type)
- if m:
- encoding = m.group(1)
- try:
- data = data.decode(encoding)
- except UnicodeError: # pragma: no cover
- data = data.decode('latin-1') # fallback
- result = Page(data, final_url)
- self._page_cache[final_url] = result
- except HTTPError as e:
- if e.code != 404:
- logger.exception('Fetch failed: %s: %s', url, e)
- except URLError as e: # pragma: no cover
- logger.exception('Fetch failed: %s: %s', url, e)
- with self._lock:
- self._bad_hosts.add(host)
- except Exception as e: # pragma: no cover
- logger.exception('Fetch failed: %s: %s', url, e)
- finally:
- self._page_cache[url] = result # even if None (failure)
- return result
-
- _distname_re = re.compile('<a href=[^>]*>([^<]+)<')
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- result = set()
- page = self.get_page(self.base_url)
- if not page:
- raise DistlibException('Unable to get %s' % self.base_url)
- for match in self._distname_re.finditer(page.data):
- result.add(match.group(1))
- return result
-
-
-class DirectoryLocator(Locator):
- """
- This class locates distributions in a directory tree.
- """
-
- def __init__(self, path, **kwargs):
- """
- Initialise an instance.
- :param path: The root of the directory tree to search.
- :param kwargs: Passed to the superclass constructor,
- except for:
- * recursive - if True (the default), subdirectories are
- recursed into. If False, only the top-level directory
- is searched,
- """
- self.recursive = kwargs.pop('recursive', True)
- super(DirectoryLocator, self).__init__(**kwargs)
- path = os.path.abspath(path)
- if not os.path.isdir(path): # pragma: no cover
- raise DistlibException('Not a directory: %r' % path)
- self.base_dir = path
-
- def should_include(self, filename, parent):
- """
- Should a filename be considered as a candidate for a distribution
- archive? As well as the filename, the directory which contains it
- is provided, though not used by the current implementation.
- """
- return filename.endswith(self.downloadable_extensions)
-
- def _get_project(self, name):
- result = {'urls': {}, 'digests': {}}
- for root, dirs, files in os.walk(self.base_dir):
- for fn in files:
- if self.should_include(fn, root):
- fn = os.path.join(root, fn)
- url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
- info = self.convert_url_to_download_info(url, name)
- if info:
- self._update_version_data(result, info)
- if not self.recursive:
- break
- return result
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- result = set()
- for root, dirs, files in os.walk(self.base_dir):
- for fn in files:
- if self.should_include(fn, root):
- fn = os.path.join(root, fn)
- url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
- info = self.convert_url_to_download_info(url, None)
- if info:
- result.add(info['name'])
- if not self.recursive:
- break
- return result
-
-
-class JSONLocator(Locator):
- """
- This locator uses special extended metadata (not available on PyPI) and is
- the basis of performant dependency resolution in distlib. Other locators
- require archive downloads before dependencies can be determined! As you
- might imagine, that can be slow.
- """
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- raise NotImplementedError('Not available from this locator')
-
- def _get_project(self, name):
- result = {'urls': {}, 'digests': {}}
- data = get_project_data(name)
- if data:
- for info in data.get('files', []):
- if info['ptype'] != 'sdist' or info['pyversion'] != 'source':
- continue
- # We don't store summary in project metadata as it makes
- # the data bigger for no benefit during dependency
- # resolution
- dist = make_dist(data['name'],
- info['version'],
- summary=data.get('summary', 'Placeholder for summary'),
- scheme=self.scheme)
- md = dist.metadata
- md.source_url = info['url']
- # TODO SHA256 digest
- if 'digest' in info and info['digest']:
- dist.digest = ('md5', info['digest'])
- md.dependencies = info.get('requirements', {})
- dist.exports = info.get('exports', {})
- result[dist.version] = dist
- result['urls'].setdefault(dist.version, set()).add(info['url'])
- return result
-
-
-class DistPathLocator(Locator):
- """
- This locator finds installed distributions in a path. It can be useful for
- adding to an :class:`AggregatingLocator`.
- """
-
- def __init__(self, distpath, **kwargs):
- """
- Initialise an instance.
-
- :param distpath: A :class:`DistributionPath` instance to search.
- """
- super(DistPathLocator, self).__init__(**kwargs)
- assert isinstance(distpath, DistributionPath)
- self.distpath = distpath
-
- def _get_project(self, name):
- dist = self.distpath.get_distribution(name)
- if dist is None:
- result = {'urls': {}, 'digests': {}}
- else:
- result = {
- dist.version: dist,
- 'urls': {
- dist.version: set([dist.source_url])
- },
- 'digests': {
- dist.version: set([None])
- }
- }
- return result
-
-
-class AggregatingLocator(Locator):
- """
- This class allows you to chain and/or merge a list of locators.
- """
-
- def __init__(self, *locators, **kwargs):
- """
- Initialise an instance.
-
- :param locators: The list of locators to search.
- :param kwargs: Passed to the superclass constructor,
- except for:
- * merge - if False (the default), the first successful
- search from any of the locators is returned. If True,
- the results from all locators are merged (this can be
- slow).
- """
- self.merge = kwargs.pop('merge', False)
- self.locators = locators
- super(AggregatingLocator, self).__init__(**kwargs)
-
- def clear_cache(self):
- super(AggregatingLocator, self).clear_cache()
- for locator in self.locators:
- locator.clear_cache()
-
- def _set_scheme(self, value):
- self._scheme = value
- for locator in self.locators:
- locator.scheme = value
-
- scheme = property(Locator.scheme.fget, _set_scheme)
-
- def _get_project(self, name):
- result = {}
- for locator in self.locators:
- d = locator.get_project(name)
- if d:
- if self.merge:
- files = result.get('urls', {})
- digests = result.get('digests', {})
- # next line could overwrite result['urls'], result['digests']
- result.update(d)
- df = result.get('urls')
- if files and df:
- for k, v in files.items():
- if k in df:
- df[k] |= v
- else:
- df[k] = v
- dd = result.get('digests')
- if digests and dd:
- dd.update(digests)
- else:
- # See issue #18. If any dists are found and we're looking
- # for specific constraints, we only return something if
- # a match is found. For example, if a DirectoryLocator
- # returns just foo (1.0) while we're looking for
- # foo (>= 2.0), we'll pretend there was nothing there so
- # that subsequent locators can be queried. Otherwise we
- # would just return foo (1.0) which would then lead to a
- # failure to find foo (>= 2.0), because other locators
- # weren't searched. Note that this only matters when
- # merge=False.
- if self.matcher is None:
- found = True
- else:
- found = False
- for k in d:
- if self.matcher.match(k):
- found = True
- break
- if found:
- result = d
- break
- return result
-
- def get_distribution_names(self):
- """
- Return all the distribution names known to this locator.
- """
- result = set()
- for locator in self.locators:
- try:
- result |= locator.get_distribution_names()
- except NotImplementedError:
- pass
- return result
-
-
-# We use a legacy scheme simply because most of the dists on PyPI use legacy
-# versions which don't conform to PEP 440.
-default_locator = AggregatingLocator(
- # JSONLocator(), # don't use as PEP 426 is withdrawn
- SimpleScrapingLocator('https://pypi.org/simple/', timeout=3.0),
- scheme='legacy')
-
-locate = default_locator.locate
-
-
-class DependencyFinder(object):
- """
- Locate dependencies for distributions.
- """
-
- def __init__(self, locator=None):
- """
- Initialise an instance, using the specified locator
- to locate distributions.
- """
- self.locator = locator or default_locator
- self.scheme = get_scheme(self.locator.scheme)
-
- def add_distribution(self, dist):
- """
- Add a distribution to the finder. This will update internal information
- about who provides what.
- :param dist: The distribution to add.
- """
- logger.debug('adding distribution %s', dist)
- name = dist.key
- self.dists_by_name[name] = dist
- self.dists[(name, dist.version)] = dist
- for p in dist.provides:
- name, version = parse_name_and_version(p)
- logger.debug('Add to provided: %s, %s, %s', name, version, dist)
- self.provided.setdefault(name, set()).add((version, dist))
-
- def remove_distribution(self, dist):
- """
- Remove a distribution from the finder. This will update internal
- information about who provides what.
- :param dist: The distribution to remove.
- """
- logger.debug('removing distribution %s', dist)
- name = dist.key
- del self.dists_by_name[name]
- del self.dists[(name, dist.version)]
- for p in dist.provides:
- name, version = parse_name_and_version(p)
- logger.debug('Remove from provided: %s, %s, %s', name, version, dist)
- s = self.provided[name]
- s.remove((version, dist))
- if not s:
- del self.provided[name]
-
- def get_matcher(self, reqt):
- """
- Get a version matcher for a requirement.
- :param reqt: The requirement
- :type reqt: str
- :return: A version matcher (an instance of
- :class:`distlib.version.Matcher`).
- """
- try:
- matcher = self.scheme.matcher(reqt)
- except UnsupportedVersionError: # pragma: no cover
- # XXX compat-mode if cannot read the version
- name = reqt.split()[0]
- matcher = self.scheme.matcher(name)
- return matcher
-
- def find_providers(self, reqt):
- """
- Find the distributions which can fulfill a requirement.
-
- :param reqt: The requirement.
- :type reqt: str
- :return: A set of distribution which can fulfill the requirement.
- """
- matcher = self.get_matcher(reqt)
- name = matcher.key # case-insensitive
- result = set()
- provided = self.provided
- if name in provided:
- for version, provider in provided[name]:
- try:
- match = matcher.match(version)
- except UnsupportedVersionError:
- match = False
-
- if match:
- result.add(provider)
- break
- return result
-
- def try_to_replace(self, provider, other, problems):
- """
- Attempt to replace one provider with another. This is typically used
- when resolving dependencies from multiple sources, e.g. A requires
- (B >= 1.0) while C requires (B >= 1.1).
-
- For successful replacement, ``provider`` must meet all the requirements
- which ``other`` fulfills.
-
- :param provider: The provider we are trying to replace with.
- :param other: The provider we're trying to replace.
- :param problems: If False is returned, this will contain what
- problems prevented replacement. This is currently
- a tuple of the literal string 'cantreplace',
- ``provider``, ``other`` and the set of requirements
- that ``provider`` couldn't fulfill.
- :return: True if we can replace ``other`` with ``provider``, else
- False.
- """
- rlist = self.reqts[other]
- unmatched = set()
- for s in rlist:
- matcher = self.get_matcher(s)
- if not matcher.match(provider.version):
- unmatched.add(s)
- if unmatched:
- # can't replace other with provider
- problems.add(('cantreplace', provider, other, frozenset(unmatched)))
- result = False
- else:
- # can replace other with provider
- self.remove_distribution(other)
- del self.reqts[other]
- for s in rlist:
- self.reqts.setdefault(provider, set()).add(s)
- self.add_distribution(provider)
- result = True
- return result
-
- def find(self, requirement, meta_extras=None, prereleases=False):
- """
- Find a distribution and all distributions it depends on.
-
- :param requirement: The requirement specifying the distribution to
- find, or a Distribution instance.
- :param meta_extras: A list of meta extras such as :test:, :build: and
- so on.
- :param prereleases: If ``True``, allow pre-release versions to be
- returned - otherwise, don't return prereleases
- unless they're all that's available.
-
- Return a set of :class:`Distribution` instances and a set of
- problems.
-
- The distributions returned should be such that they have the
- :attr:`required` attribute set to ``True`` if they were
- from the ``requirement`` passed to ``find()``, and they have the
- :attr:`build_time_dependency` attribute set to ``True`` unless they
- are post-installation dependencies of the ``requirement``.
-
- The problems should be a tuple consisting of the string
- ``'unsatisfied'`` and the requirement which couldn't be satisfied
- by any distribution known to the locator.
- """
-
- self.provided = {}
- self.dists = {}
- self.dists_by_name = {}
- self.reqts = {}
-
- meta_extras = set(meta_extras or [])
- if ':*:' in meta_extras:
- meta_extras.remove(':*:')
- # :meta: and :run: are implicitly included
- meta_extras |= set([':test:', ':build:', ':dev:'])
-
- if isinstance(requirement, Distribution):
- dist = odist = requirement
- logger.debug('passed %s as requirement', odist)
- else:
- dist = odist = self.locator.locate(requirement, prereleases=prereleases)
- if dist is None:
- raise DistlibException('Unable to locate %r' % requirement)
- logger.debug('located %s', odist)
- dist.requested = True
- problems = set()
- todo = set([dist])
- install_dists = set([odist])
- while todo:
- dist = todo.pop()
- name = dist.key # case-insensitive
- if name not in self.dists_by_name:
- self.add_distribution(dist)
- else:
- # import pdb; pdb.set_trace()
- other = self.dists_by_name[name]
- if other != dist:
- self.try_to_replace(dist, other, problems)
-
- ireqts = dist.run_requires | dist.meta_requires
- sreqts = dist.build_requires
- ereqts = set()
- if meta_extras and dist in install_dists:
- for key in ('test', 'build', 'dev'):
- e = ':%s:' % key
- if e in meta_extras:
- ereqts |= getattr(dist, '%s_requires' % key)
- all_reqts = ireqts | sreqts | ereqts
- for r in all_reqts:
- providers = self.find_providers(r)
- if not providers:
- logger.debug('No providers found for %r', r)
- provider = self.locator.locate(r, prereleases=prereleases)
- # If no provider is found and we didn't consider
- # prereleases, consider them now.
- if provider is None and not prereleases:
- provider = self.locator.locate(r, prereleases=True)
- if provider is None:
- logger.debug('Cannot satisfy %r', r)
- problems.add(('unsatisfied', r))
- else:
- n, v = provider.key, provider.version
- if (n, v) not in self.dists:
- todo.add(provider)
- providers.add(provider)
- if r in ireqts and dist in install_dists:
- install_dists.add(provider)
- logger.debug('Adding %s to install_dists', provider.name_and_version)
- for p in providers:
- name = p.key
- if name not in self.dists_by_name:
- self.reqts.setdefault(p, set()).add(r)
- else:
- other = self.dists_by_name[name]
- if other != p:
- # see if other can be replaced by p
- self.try_to_replace(p, other, problems)
-
- dists = set(self.dists.values())
- for dist in dists:
- dist.build_time_dependency = dist not in install_dists
- if dist.build_time_dependency:
- logger.debug('%s is a build-time dependency only.', dist.name_and_version)
- logger.debug('find done for %s', odist)
- return dists, problems
diff --git a/contrib/python/pip/pip/_vendor/distlib/manifest.py b/contrib/python/pip/pip/_vendor/distlib/manifest.py
deleted file mode 100644
index 420dcf12ed2..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/manifest.py
+++ /dev/null
@@ -1,384 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012-2023 Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""
-Class representing the list of files in a distribution.
-
-Equivalent to distutils.filelist, but fixes some problems.
-"""
-import fnmatch
-import logging
-import os
-import re
-import sys
-
-from . import DistlibException
-from .compat import fsdecode
-from .util import convert_path
-
-
-__all__ = ['Manifest']
-
-logger = logging.getLogger(__name__)
-
-# a \ followed by some spaces + EOL
-_COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M)
-_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
-
-#
-# Due to the different results returned by fnmatch.translate, we need
-# to do slightly different processing for Python 2.7 and 3.2 ... this needed
-# to be brought in for Python 3.6 onwards.
-#
-_PYTHON_VERSION = sys.version_info[:2]
-
-
-class Manifest(object):
- """
- A list of files built by exploring the filesystem and filtered by applying various
- patterns to what we find there.
- """
-
- def __init__(self, base=None):
- """
- Initialise an instance.
-
- :param base: The base directory to explore under.
- """
- self.base = os.path.abspath(os.path.normpath(base or os.getcwd()))
- self.prefix = self.base + os.sep
- self.allfiles = None
- self.files = set()
-
- #
- # Public API
- #
-
- def findall(self):
- """Find all files under the base and set ``allfiles`` to the absolute
- pathnames of files found.
- """
- from stat import S_ISREG, S_ISDIR, S_ISLNK
-
- self.allfiles = allfiles = []
- root = self.base
- stack = [root]
- pop = stack.pop
- push = stack.append
-
- while stack:
- root = pop()
- names = os.listdir(root)
-
- for name in names:
- fullname = os.path.join(root, name)
-
- # Avoid excess stat calls -- just one will do, thank you!
- stat = os.stat(fullname)
- mode = stat.st_mode
- if S_ISREG(mode):
- allfiles.append(fsdecode(fullname))
- elif S_ISDIR(mode) and not S_ISLNK(mode):
- push(fullname)
-
- def add(self, item):
- """
- Add a file to the manifest.
-
- :param item: The pathname to add. This can be relative to the base.
- """
- if not item.startswith(self.prefix):
- item = os.path.join(self.base, item)
- self.files.add(os.path.normpath(item))
-
- def add_many(self, items):
- """
- Add a list of files to the manifest.
-
- :param items: The pathnames to add. These can be relative to the base.
- """
- for item in items:
- self.add(item)
-
- def sorted(self, wantdirs=False):
- """
- Return sorted files in directory order
- """
-
- def add_dir(dirs, d):
- dirs.add(d)
- logger.debug('add_dir added %s', d)
- if d != self.base:
- parent, _ = os.path.split(d)
- assert parent not in ('', '/')
- add_dir(dirs, parent)
-
- result = set(self.files) # make a copy!
- if wantdirs:
- dirs = set()
- for f in result:
- add_dir(dirs, os.path.dirname(f))
- result |= dirs
- return [os.path.join(*path_tuple) for path_tuple in
- sorted(os.path.split(path) for path in result)]
-
- def clear(self):
- """Clear all collected files."""
- self.files = set()
- self.allfiles = []
-
- def process_directive(self, directive):
- """
- Process a directive which either adds some files from ``allfiles`` to
- ``files``, or removes some files from ``files``.
-
- :param directive: The directive to process. This should be in a format
- compatible with distutils ``MANIFEST.in`` files:
-
- http://docs.python.org/distutils/sourcedist.html#commands
- """
- # Parse the line: split it up, make sure the right number of words
- # is there, and return the relevant words. 'action' is always
- # defined: it's the first word of the line. Which of the other
- # three are defined depends on the action; it'll be either
- # patterns, (dir and patterns), or (dirpattern).
- action, patterns, thedir, dirpattern = self._parse_directive(directive)
-
- # OK, now we know that the action is valid and we have the
- # right number of words on the line for that action -- so we
- # can proceed with minimal error-checking.
- if action == 'include':
- for pattern in patterns:
- if not self._include_pattern(pattern, anchor=True):
- logger.warning('no files found matching %r', pattern)
-
- elif action == 'exclude':
- for pattern in patterns:
- self._exclude_pattern(pattern, anchor=True)
-
- elif action == 'global-include':
- for pattern in patterns:
- if not self._include_pattern(pattern, anchor=False):
- logger.warning('no files found matching %r '
- 'anywhere in distribution', pattern)
-
- elif action == 'global-exclude':
- for pattern in patterns:
- self._exclude_pattern(pattern, anchor=False)
-
- elif action == 'recursive-include':
- for pattern in patterns:
- if not self._include_pattern(pattern, prefix=thedir):
- logger.warning('no files found matching %r '
- 'under directory %r', pattern, thedir)
-
- elif action == 'recursive-exclude':
- for pattern in patterns:
- self._exclude_pattern(pattern, prefix=thedir)
-
- elif action == 'graft':
- if not self._include_pattern(None, prefix=dirpattern):
- logger.warning('no directories found matching %r',
- dirpattern)
-
- elif action == 'prune':
- if not self._exclude_pattern(None, prefix=dirpattern):
- logger.warning('no previously-included directories found '
- 'matching %r', dirpattern)
- else: # pragma: no cover
- # This should never happen, as it should be caught in
- # _parse_template_line
- raise DistlibException(
- 'invalid action %r' % action)
-
- #
- # Private API
- #
-
- def _parse_directive(self, directive):
- """
- Validate a directive.
- :param directive: The directive to validate.
- :return: A tuple of action, patterns, thedir, dir_patterns
- """
- words = directive.split()
- if len(words) == 1 and words[0] not in ('include', 'exclude',
- 'global-include',
- 'global-exclude',
- 'recursive-include',
- 'recursive-exclude',
- 'graft', 'prune'):
- # no action given, let's use the default 'include'
- words.insert(0, 'include')
-
- action = words[0]
- patterns = thedir = dir_pattern = None
-
- if action in ('include', 'exclude',
- 'global-include', 'global-exclude'):
- if len(words) < 2:
- raise DistlibException(
- '%r expects <pattern1> <pattern2> ...' % action)
-
- patterns = [convert_path(word) for word in words[1:]]
-
- elif action in ('recursive-include', 'recursive-exclude'):
- if len(words) < 3:
- raise DistlibException(
- '%r expects <dir> <pattern1> <pattern2> ...' % action)
-
- thedir = convert_path(words[1])
- patterns = [convert_path(word) for word in words[2:]]
-
- elif action in ('graft', 'prune'):
- if len(words) != 2:
- raise DistlibException(
- '%r expects a single <dir_pattern>' % action)
-
- dir_pattern = convert_path(words[1])
-
- else:
- raise DistlibException('unknown action %r' % action)
-
- return action, patterns, thedir, dir_pattern
-
- def _include_pattern(self, pattern, anchor=True, prefix=None,
- is_regex=False):
- """Select strings (presumably filenames) from 'self.files' that
- match 'pattern', a Unix-style wildcard (glob) pattern.
-
- Patterns are not quite the same as implemented by the 'fnmatch'
- module: '*' and '?' match non-special characters, where "special"
- is platform-dependent: slash on Unix; colon, slash, and backslash on
- DOS/Windows; and colon on Mac OS.
-
- If 'anchor' is true (the default), then the pattern match is more
- stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
- 'anchor' is false, both of these will match.
-
- If 'prefix' is supplied, then only filenames starting with 'prefix'
- (itself a pattern) and ending with 'pattern', with anything in between
- them, will match. 'anchor' is ignored in this case.
-
- If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and
- 'pattern' is assumed to be either a string containing a regex or a
- regex object -- no translation is done, the regex is just compiled
- and used as-is.
-
- Selected strings will be added to self.files.
-
- Return True if files are found.
- """
- # XXX docstring lying about what the special chars are?
- found = False
- pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
-
- # delayed loading of allfiles list
- if self.allfiles is None:
- self.findall()
-
- for name in self.allfiles:
- if pattern_re.search(name):
- self.files.add(name)
- found = True
- return found
-
- def _exclude_pattern(self, pattern, anchor=True, prefix=None,
- is_regex=False):
- """Remove strings (presumably filenames) from 'files' that match
- 'pattern'.
-
- Other parameters are the same as for 'include_pattern()', above.
- The list 'self.files' is modified in place. Return True if files are
- found.
-
- This API is public to allow e.g. exclusion of SCM subdirs, e.g. when
- packaging source distributions
- """
- found = False
- pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
- for f in list(self.files):
- if pattern_re.search(f):
- self.files.remove(f)
- found = True
- return found
-
- def _translate_pattern(self, pattern, anchor=True, prefix=None,
- is_regex=False):
- """Translate a shell-like wildcard pattern to a compiled regular
- expression.
-
- Return the compiled regex. If 'is_regex' true,
- then 'pattern' is directly compiled to a regex (if it's a string)
- or just returned as-is (assumes it's a regex object).
- """
- if is_regex:
- if isinstance(pattern, str):
- return re.compile(pattern)
- else:
- return pattern
-
- if _PYTHON_VERSION > (3, 2):
- # ditch start and end characters
- start, _, end = self._glob_to_re('_').partition('_')
-
- if pattern:
- pattern_re = self._glob_to_re(pattern)
- if _PYTHON_VERSION > (3, 2):
- assert pattern_re.startswith(start) and pattern_re.endswith(end)
- else:
- pattern_re = ''
-
- base = re.escape(os.path.join(self.base, ''))
- if prefix is not None:
- # ditch end of pattern character
- if _PYTHON_VERSION <= (3, 2):
- empty_pattern = self._glob_to_re('')
- prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)]
- else:
- prefix_re = self._glob_to_re(prefix)
- assert prefix_re.startswith(start) and prefix_re.endswith(end)
- prefix_re = prefix_re[len(start): len(prefix_re) - len(end)]
- sep = os.sep
- if os.sep == '\\':
- sep = r'\\'
- if _PYTHON_VERSION <= (3, 2):
- pattern_re = '^' + base + sep.join((prefix_re,
- '.*' + pattern_re))
- else:
- pattern_re = pattern_re[len(start): len(pattern_re) - len(end)]
- pattern_re = r'%s%s%s%s.*%s%s' % (start, base, prefix_re, sep,
- pattern_re, end)
- else: # no prefix -- respect anchor flag
- if anchor:
- if _PYTHON_VERSION <= (3, 2):
- pattern_re = '^' + base + pattern_re
- else:
- pattern_re = r'%s%s%s' % (start, base, pattern_re[len(start):])
-
- return re.compile(pattern_re)
-
- def _glob_to_re(self, pattern):
- """Translate a shell-like glob pattern to a regular expression.
-
- Return a string containing the regex. Differs from
- 'fnmatch.translate()' in that '*' does not match "special characters"
- (which are platform-specific).
- """
- pattern_re = fnmatch.translate(pattern)
-
- # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
- # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
- # and by extension they shouldn't match such "special characters" under
- # any OS. So change all non-escaped dots in the RE to match any
- # character except the special characters (currently: just os.sep).
- sep = os.sep
- if os.sep == '\\':
- # we're using a regex to manipulate a regex, so we need
- # to escape the backslash twice
- sep = r'\\\\'
- escaped = r'\1[^%s]' % sep
- pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
- return pattern_re
diff --git a/contrib/python/pip/pip/_vendor/distlib/markers.py b/contrib/python/pip/pip/_vendor/distlib/markers.py
deleted file mode 100644
index 3f5632be47c..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/markers.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012-2023 Vinay Sajip.
-# Licensed to the Python Software Foundation under a contributor agreement.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""
-Parser for the environment markers micro-language defined in PEP 508.
-"""
-
-# Note: In PEP 345, the micro-language was Python compatible, so the ast
-# module could be used to parse it. However, PEP 508 introduced operators such
-# as ~= and === which aren't in Python, necessitating a different approach.
-
-import os
-import re
-import sys
-import platform
-
-from .compat import string_types
-from .util import in_venv, parse_marker
-from .version import LegacyVersion as LV
-
-__all__ = ['interpret']
-
-_VERSION_PATTERN = re.compile(r'((\d+(\.\d+)*\w*)|\'(\d+(\.\d+)*\w*)\'|\"(\d+(\.\d+)*\w*)\")')
-_VERSION_MARKERS = {'python_version', 'python_full_version'}
-
-
-def _is_version_marker(s):
- return isinstance(s, string_types) and s in _VERSION_MARKERS
-
-
-def _is_literal(o):
- if not isinstance(o, string_types) or not o:
- return False
- return o[0] in '\'"'
-
-
-def _get_versions(s):
- return {LV(m.groups()[0]) for m in _VERSION_PATTERN.finditer(s)}
-
-
-class Evaluator(object):
- """
- This class is used to evaluate marker expressions.
- """
-
- operations = {
- '==': lambda x, y: x == y,
- '===': lambda x, y: x == y,
- '~=': lambda x, y: x == y or x > y,
- '!=': lambda x, y: x != y,
- '<': lambda x, y: x < y,
- '<=': lambda x, y: x == y or x < y,
- '>': lambda x, y: x > y,
- '>=': lambda x, y: x == y or x > y,
- 'and': lambda x, y: x and y,
- 'or': lambda x, y: x or y,
- 'in': lambda x, y: x in y,
- 'not in': lambda x, y: x not in y,
- }
-
- def evaluate(self, expr, context):
- """
- Evaluate a marker expression returned by the :func:`parse_requirement`
- function in the specified context.
- """
- if isinstance(expr, string_types):
- if expr[0] in '\'"':
- result = expr[1:-1]
- else:
- if expr not in context:
- raise SyntaxError('unknown variable: %s' % expr)
- result = context[expr]
- else:
- assert isinstance(expr, dict)
- op = expr['op']
- if op not in self.operations:
- raise NotImplementedError('op not implemented: %s' % op)
- elhs = expr['lhs']
- erhs = expr['rhs']
- if _is_literal(expr['lhs']) and _is_literal(expr['rhs']):
- raise SyntaxError('invalid comparison: %s %s %s' % (elhs, op, erhs))
-
- lhs = self.evaluate(elhs, context)
- rhs = self.evaluate(erhs, context)
- if ((_is_version_marker(elhs) or _is_version_marker(erhs)) and
- op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')):
- lhs = LV(lhs)
- rhs = LV(rhs)
- elif _is_version_marker(elhs) and op in ('in', 'not in'):
- lhs = LV(lhs)
- rhs = _get_versions(rhs)
- result = self.operations[op](lhs, rhs)
- return result
-
-
-_DIGITS = re.compile(r'\d+\.\d+')
-
-
-def default_context():
-
- def format_full_version(info):
- version = '%s.%s.%s' % (info.major, info.minor, info.micro)
- kind = info.releaselevel
- if kind != 'final':
- version += kind[0] + str(info.serial)
- return version
-
- if hasattr(sys, 'implementation'):
- implementation_version = format_full_version(sys.implementation.version)
- implementation_name = sys.implementation.name
- else:
- implementation_version = '0'
- implementation_name = ''
-
- ppv = platform.python_version()
- m = _DIGITS.match(ppv)
- pv = m.group(0)
- result = {
- 'implementation_name': implementation_name,
- 'implementation_version': implementation_version,
- 'os_name': os.name,
- 'platform_machine': platform.machine(),
- 'platform_python_implementation': platform.python_implementation(),
- 'platform_release': platform.release(),
- 'platform_system': platform.system(),
- 'platform_version': platform.version(),
- 'platform_in_venv': str(in_venv()),
- 'python_full_version': ppv,
- 'python_version': pv,
- 'sys_platform': sys.platform,
- }
- return result
-
-
-DEFAULT_CONTEXT = default_context()
-del default_context
-
-evaluator = Evaluator()
-
-
-def interpret(marker, execution_context=None):
- """
- Interpret a marker and return a result depending on environment.
-
- :param marker: The marker to interpret.
- :type marker: str
- :param execution_context: The context used for name lookup.
- :type execution_context: mapping
- """
- try:
- expr, rest = parse_marker(marker)
- except Exception as e:
- raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e))
- if rest and rest[0] != '#':
- raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest))
- context = dict(DEFAULT_CONTEXT)
- if execution_context:
- context.update(execution_context)
- return evaluator.evaluate(expr, context)
diff --git a/contrib/python/pip/pip/_vendor/distlib/metadata.py b/contrib/python/pip/pip/_vendor/distlib/metadata.py
deleted file mode 100644
index ce9a34b3e24..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/metadata.py
+++ /dev/null
@@ -1,1031 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Implementation of the Metadata for Python packages PEPs.
-
-Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and 2.2).
-"""
-from __future__ import unicode_literals
-
-import codecs
-from email import message_from_file
-import json
-import logging
-import re
-
-from . import DistlibException, __version__
-from .compat import StringIO, string_types, text_type
-from .markers import interpret
-from .util import extract_by_key, get_extras
-from .version import get_scheme, PEP440_VERSION_RE
-
-logger = logging.getLogger(__name__)
-
-
-class MetadataMissingError(DistlibException):
- """A required metadata is missing"""
-
-
-class MetadataConflictError(DistlibException):
- """Attempt to read or write metadata fields that are conflictual."""
-
-
-class MetadataUnrecognizedVersionError(DistlibException):
- """Unknown metadata version number."""
-
-
-class MetadataInvalidError(DistlibException):
- """A metadata value is invalid"""
-
-
-# public API of this module
-__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION']
-
-# Encoding used for the PKG-INFO files
-PKG_INFO_ENCODING = 'utf-8'
-
-# preferred version. Hopefully will be changed
-# to 1.2 once PEP 345 is supported everywhere
-PKG_INFO_PREFERRED_VERSION = '1.1'
-
-_LINE_PREFIX_1_2 = re.compile('\n \\|')
-_LINE_PREFIX_PRE_1_2 = re.compile('\n ')
-_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Summary', 'Description', 'Keywords', 'Home-page',
- 'Author', 'Author-email', 'License')
-
-_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
- 'Keywords', 'Home-page', 'Author', 'Author-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes',
- 'Provides', 'Requires')
-
-_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', 'Download-URL')
-
-_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
- 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
- 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
- 'Requires-Python', 'Requires-External')
-
-_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Obsoletes-Dist', 'Requires-External',
- 'Maintainer', 'Maintainer-email', 'Project-URL')
-
-_426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
- 'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
- 'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
- 'Requires-Python', 'Requires-External', 'Private-Version', 'Obsoleted-By', 'Setup-Requires-Dist',
- 'Extension', 'Provides-Extra')
-
-_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension')
-
-# See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in
-# the metadata. Include them in the tuple literal below to allow them
-# (for now).
-# Ditto for Obsoletes - see issue #140.
-_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires', 'Provides', 'Obsoletes')
-
-_566_MARKERS = ('Description-Content-Type', )
-
-_643_MARKERS = ('Dynamic', 'License-File')
-
-_643_FIELDS = _566_FIELDS + _643_MARKERS
-
-_ALL_FIELDS = set()
-_ALL_FIELDS.update(_241_FIELDS)
-_ALL_FIELDS.update(_314_FIELDS)
-_ALL_FIELDS.update(_345_FIELDS)
-_ALL_FIELDS.update(_426_FIELDS)
-_ALL_FIELDS.update(_566_FIELDS)
-_ALL_FIELDS.update(_643_FIELDS)
-
-EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''')
-
-
-def _version2fieldlist(version):
- if version == '1.0':
- return _241_FIELDS
- elif version == '1.1':
- return _314_FIELDS
- elif version == '1.2':
- return _345_FIELDS
- elif version in ('1.3', '2.1'):
- # avoid adding field names if already there
- return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS)
- elif version == '2.0':
- raise ValueError('Metadata 2.0 is withdrawn and not supported')
- # return _426_FIELDS
- elif version == '2.2':
- return _643_FIELDS
- raise MetadataUnrecognizedVersionError(version)
-
-
-def _best_version(fields):
- """Detect the best version depending on the fields used."""
-
- def _has_marker(keys, markers):
- return any(marker in keys for marker in markers)
-
- keys = [key for key, value in fields.items() if value not in ([], 'UNKNOWN', None)]
- possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2'] # 2.0 removed
-
- # first let's try to see if a field is not part of one of the version
- for key in keys:
- if key not in _241_FIELDS and '1.0' in possible_versions:
- possible_versions.remove('1.0')
- logger.debug('Removed 1.0 due to %s', key)
- if key not in _314_FIELDS and '1.1' in possible_versions:
- possible_versions.remove('1.1')
- logger.debug('Removed 1.1 due to %s', key)
- if key not in _345_FIELDS and '1.2' in possible_versions:
- possible_versions.remove('1.2')
- logger.debug('Removed 1.2 due to %s', key)
- if key not in _566_FIELDS and '1.3' in possible_versions:
- possible_versions.remove('1.3')
- logger.debug('Removed 1.3 due to %s', key)
- if key not in _566_FIELDS and '2.1' in possible_versions:
- if key != 'Description': # In 2.1, description allowed after headers
- possible_versions.remove('2.1')
- logger.debug('Removed 2.1 due to %s', key)
- if key not in _643_FIELDS and '2.2' in possible_versions:
- possible_versions.remove('2.2')
- logger.debug('Removed 2.2 due to %s', key)
- # if key not in _426_FIELDS and '2.0' in possible_versions:
- # possible_versions.remove('2.0')
- # logger.debug('Removed 2.0 due to %s', key)
-
- # possible_version contains qualified versions
- if len(possible_versions) == 1:
- return possible_versions[0] # found !
- elif len(possible_versions) == 0:
- logger.debug('Out of options - unknown metadata set: %s', fields)
- raise MetadataConflictError('Unknown metadata set')
-
- # let's see if one unique marker is found
- is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS)
- is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS)
- is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS)
- # is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS)
- is_2_2 = '2.2' in possible_versions and _has_marker(keys, _643_MARKERS)
- if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_2) > 1:
- raise MetadataConflictError('You used incompatible 1.1/1.2/2.1/2.2 fields')
-
- # we have the choice, 1.0, or 1.2, 2.1 or 2.2
- # - 1.0 has a broken Summary field but works with all tools
- # - 1.1 is to avoid
- # - 1.2 fixes Summary but has little adoption
- # - 2.1 adds more features
- # - 2.2 is the latest
- if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_2:
- # we couldn't find any specific marker
- if PKG_INFO_PREFERRED_VERSION in possible_versions:
- return PKG_INFO_PREFERRED_VERSION
- if is_1_1:
- return '1.1'
- if is_1_2:
- return '1.2'
- if is_2_1:
- return '2.1'
- # if is_2_2:
- # return '2.2'
-
- return '2.2'
-
-
-# This follows the rules about transforming keys as described in
-# https://www.python.org/dev/peps/pep-0566/#id17
-_ATTR2FIELD = {name.lower().replace("-", "_"): name for name in _ALL_FIELDS}
-_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()}
-
-_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist')
-_VERSIONS_FIELDS = ('Requires-Python', )
-_VERSION_FIELDS = ('Version', )
-_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', 'Requires', 'Provides', 'Obsoletes-Dist', 'Provides-Dist',
- 'Requires-Dist', 'Requires-External', 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist',
- 'Provides-Extra', 'Extension', 'License-File')
-_LISTTUPLEFIELDS = ('Project-URL', )
-
-_ELEMENTSFIELD = ('Keywords', )
-
-_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description')
-
-_MISSING = object()
-
-_FILESAFE = re.compile('[^A-Za-z0-9.]+')
-
-
-def _get_name_and_version(name, version, for_filename=False):
- """Return the distribution name with version.
-
- If for_filename is true, return a filename-escaped form."""
- if for_filename:
- # For both name and version any runs of non-alphanumeric or '.'
- # characters are replaced with a single '-'. Additionally any
- # spaces in the version string become '.'
- name = _FILESAFE.sub('-', name)
- version = _FILESAFE.sub('-', version.replace(' ', '.'))
- return '%s-%s' % (name, version)
-
-
-class LegacyMetadata(object):
- """The legacy metadata of a release.
-
- Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can
- instantiate the class with one of these arguments (or none):
- - *path*, the path to a metadata file
- - *fileobj* give a file-like object with metadata as content
- - *mapping* is a dict-like object
- - *scheme* is a version scheme name
- """
-
- # TODO document the mapping API and UNKNOWN default key
-
- def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
- if [path, fileobj, mapping].count(None) < 2:
- raise TypeError('path, fileobj and mapping are exclusive')
- self._fields = {}
- self.requires_files = []
- self._dependencies = None
- self.scheme = scheme
- if path is not None:
- self.read(path)
- elif fileobj is not None:
- self.read_file(fileobj)
- elif mapping is not None:
- self.update(mapping)
- self.set_metadata_version()
-
- def set_metadata_version(self):
- self._fields['Metadata-Version'] = _best_version(self._fields)
-
- def _write_field(self, fileobj, name, value):
- fileobj.write('%s: %s\n' % (name, value))
-
- def __getitem__(self, name):
- return self.get(name)
-
- def __setitem__(self, name, value):
- return self.set(name, value)
-
- def __delitem__(self, name):
- field_name = self._convert_name(name)
- try:
- del self._fields[field_name]
- except KeyError:
- raise KeyError(name)
-
- def __contains__(self, name):
- return (name in self._fields or self._convert_name(name) in self._fields)
-
- def _convert_name(self, name):
- if name in _ALL_FIELDS:
- return name
- name = name.replace('-', '_').lower()
- return _ATTR2FIELD.get(name, name)
-
- def _default_value(self, name):
- if name in _LISTFIELDS or name in _ELEMENTSFIELD:
- return []
- return 'UNKNOWN'
-
- def _remove_line_prefix(self, value):
- if self.metadata_version in ('1.0', '1.1'):
- return _LINE_PREFIX_PRE_1_2.sub('\n', value)
- else:
- return _LINE_PREFIX_1_2.sub('\n', value)
-
- def __getattr__(self, name):
- if name in _ATTR2FIELD:
- return self[name]
- raise AttributeError(name)
-
- #
- # Public API
- #
-
- def get_fullname(self, filesafe=False):
- """
- Return the distribution name with version.
-
- If filesafe is true, return a filename-escaped form.
- """
- return _get_name_and_version(self['Name'], self['Version'], filesafe)
-
- def is_field(self, name):
- """return True if name is a valid metadata key"""
- name = self._convert_name(name)
- return name in _ALL_FIELDS
-
- def is_multi_field(self, name):
- name = self._convert_name(name)
- return name in _LISTFIELDS
-
- def read(self, filepath):
- """Read the metadata values from a file path."""
- fp = codecs.open(filepath, 'r', encoding='utf-8')
- try:
- self.read_file(fp)
- finally:
- fp.close()
-
- def read_file(self, fileob):
- """Read the metadata values from a file object."""
- msg = message_from_file(fileob)
- self._fields['Metadata-Version'] = msg['metadata-version']
-
- # When reading, get all the fields we can
- for field in _ALL_FIELDS:
- if field not in msg:
- continue
- if field in _LISTFIELDS:
- # we can have multiple lines
- values = msg.get_all(field)
- if field in _LISTTUPLEFIELDS and values is not None:
- values = [tuple(value.split(',')) for value in values]
- self.set(field, values)
- else:
- # single line
- value = msg[field]
- if value is not None and value != 'UNKNOWN':
- self.set(field, value)
-
- # PEP 566 specifies that the body be used for the description, if
- # available
- body = msg.get_payload()
- self["Description"] = body if body else self["Description"]
- # logger.debug('Attempting to set metadata for %s', self)
- # self.set_metadata_version()
-
- def write(self, filepath, skip_unknown=False):
- """Write the metadata fields to filepath."""
- fp = codecs.open(filepath, 'w', encoding='utf-8')
- try:
- self.write_file(fp, skip_unknown)
- finally:
- fp.close()
-
- def write_file(self, fileobject, skip_unknown=False):
- """Write the PKG-INFO format data to a file object."""
- self.set_metadata_version()
-
- for field in _version2fieldlist(self['Metadata-Version']):
- values = self.get(field)
- if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']):
- continue
- if field in _ELEMENTSFIELD:
- self._write_field(fileobject, field, ','.join(values))
- continue
- if field not in _LISTFIELDS:
- if field == 'Description':
- if self.metadata_version in ('1.0', '1.1'):
- values = values.replace('\n', '\n ')
- else:
- values = values.replace('\n', '\n |')
- values = [values]
-
- if field in _LISTTUPLEFIELDS:
- values = [','.join(value) for value in values]
-
- for value in values:
- self._write_field(fileobject, field, value)
-
- def update(self, other=None, **kwargs):
- """Set metadata values from the given iterable `other` and kwargs.
-
- Behavior is like `dict.update`: If `other` has a ``keys`` method,
- they are looped over and ``self[key]`` is assigned ``other[key]``.
- Else, ``other`` is an iterable of ``(key, value)`` iterables.
-
- Keys that don't match a metadata field or that have an empty value are
- dropped.
- """
-
- def _set(key, value):
- if key in _ATTR2FIELD and value:
- self.set(self._convert_name(key), value)
-
- if not other:
- # other is None or empty container
- pass
- elif hasattr(other, 'keys'):
- for k in other.keys():
- _set(k, other[k])
- else:
- for k, v in other:
- _set(k, v)
-
- if kwargs:
- for k, v in kwargs.items():
- _set(k, v)
-
- def set(self, name, value):
- """Control then set a metadata field."""
- name = self._convert_name(name)
-
- if ((name in _ELEMENTSFIELD or name == 'Platform') and not isinstance(value, (list, tuple))):
- if isinstance(value, string_types):
- value = [v.strip() for v in value.split(',')]
- else:
- value = []
- elif (name in _LISTFIELDS and not isinstance(value, (list, tuple))):
- if isinstance(value, string_types):
- value = [value]
- else:
- value = []
-
- if logger.isEnabledFor(logging.WARNING):
- project_name = self['Name']
-
- scheme = get_scheme(self.scheme)
- if name in _PREDICATE_FIELDS and value is not None:
- for v in value:
- # check that the values are valid
- if not scheme.is_valid_matcher(v.split(';')[0]):
- logger.warning("'%s': '%s' is not valid (field '%s')", project_name, v, name)
- # FIXME this rejects UNKNOWN, is that right?
- elif name in _VERSIONS_FIELDS and value is not None:
- if not scheme.is_valid_constraint_list(value):
- logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
- elif name in _VERSION_FIELDS and value is not None:
- if not scheme.is_valid_version(value):
- logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
-
- if name in _UNICODEFIELDS:
- if name == 'Description':
- value = self._remove_line_prefix(value)
-
- self._fields[name] = value
-
- def get(self, name, default=_MISSING):
- """Get a metadata field."""
- name = self._convert_name(name)
- if name not in self._fields:
- if default is _MISSING:
- default = self._default_value(name)
- return default
- if name in _UNICODEFIELDS:
- value = self._fields[name]
- return value
- elif name in _LISTFIELDS:
- value = self._fields[name]
- if value is None:
- return []
- res = []
- for val in value:
- if name not in _LISTTUPLEFIELDS:
- res.append(val)
- else:
- # That's for Project-URL
- res.append((val[0], val[1]))
- return res
-
- elif name in _ELEMENTSFIELD:
- value = self._fields[name]
- if isinstance(value, string_types):
- return value.split(',')
- return self._fields[name]
-
- def check(self, strict=False):
- """Check if the metadata is compliant. If strict is True then raise if
- no Name or Version are provided"""
- self.set_metadata_version()
-
- # XXX should check the versions (if the file was loaded)
- missing, warnings = [], []
-
- for attr in ('Name', 'Version'): # required by PEP 345
- if attr not in self:
- missing.append(attr)
-
- if strict and missing != []:
- msg = 'missing required metadata: %s' % ', '.join(missing)
- raise MetadataMissingError(msg)
-
- for attr in ('Home-page', 'Author'):
- if attr not in self:
- missing.append(attr)
-
- # checking metadata 1.2 (XXX needs to check 1.1, 1.0)
- if self['Metadata-Version'] != '1.2':
- return missing, warnings
-
- scheme = get_scheme(self.scheme)
-
- def are_valid_constraints(value):
- for v in value:
- if not scheme.is_valid_matcher(v.split(';')[0]):
- return False
- return True
-
- for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints),
- (_VERSIONS_FIELDS, scheme.is_valid_constraint_list), (_VERSION_FIELDS,
- scheme.is_valid_version)):
- for field in fields:
- value = self.get(field, None)
- if value is not None and not controller(value):
- warnings.append("Wrong value for '%s': %s" % (field, value))
-
- return missing, warnings
-
- def todict(self, skip_missing=False):
- """Return fields as a dict.
-
- Field names will be converted to use the underscore-lowercase style
- instead of hyphen-mixed case (i.e. home_page instead of Home-page).
- This is as per https://www.python.org/dev/peps/pep-0566/#id17.
- """
- self.set_metadata_version()
-
- fields = _version2fieldlist(self['Metadata-Version'])
-
- data = {}
-
- for field_name in fields:
- if not skip_missing or field_name in self._fields:
- key = _FIELD2ATTR[field_name]
- if key != 'project_url':
- data[key] = self[field_name]
- else:
- data[key] = [','.join(u) for u in self[field_name]]
-
- return data
-
- def add_requirements(self, requirements):
- if self['Metadata-Version'] == '1.1':
- # we can't have 1.1 metadata *and* Setuptools requires
- for field in ('Obsoletes', 'Requires', 'Provides'):
- if field in self:
- del self[field]
- self['Requires-Dist'] += requirements
-
- # Mapping API
- # TODO could add iter* variants
-
- def keys(self):
- return list(_version2fieldlist(self['Metadata-Version']))
-
- def __iter__(self):
- for key in self.keys():
- yield key
-
- def values(self):
- return [self[key] for key in self.keys()]
-
- def items(self):
- return [(key, self[key]) for key in self.keys()]
-
- def __repr__(self):
- return '<%s %s %s>' % (self.__class__.__name__, self.name, self.version)
-
-
-METADATA_FILENAME = 'pydist.json'
-WHEEL_METADATA_FILENAME = 'metadata.json'
-LEGACY_METADATA_FILENAME = 'METADATA'
-
-
-class Metadata(object):
- """
- The metadata of a release. This implementation uses 2.1
- metadata where possible. If not possible, it wraps a LegacyMetadata
- instance which handles the key-value metadata format.
- """
-
- METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$')
-
- NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I)
-
- FIELDNAME_MATCHER = re.compile('^[A-Z]([0-9A-Z-]*[0-9A-Z])?$', re.I)
-
- VERSION_MATCHER = PEP440_VERSION_RE
-
- SUMMARY_MATCHER = re.compile('.{1,2047}')
-
- METADATA_VERSION = '2.0'
-
- GENERATOR = 'distlib (%s)' % __version__
-
- MANDATORY_KEYS = {
- 'name': (),
- 'version': (),
- 'summary': ('legacy', ),
- }
-
- INDEX_KEYS = ('name version license summary description author '
- 'author_email keywords platform home_page classifiers '
- 'download_url')
-
- DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires '
- 'dev_requires provides meta_requires obsoleted_by '
- 'supports_environments')
-
- SYNTAX_VALIDATORS = {
- 'metadata_version': (METADATA_VERSION_MATCHER, ()),
- 'name': (NAME_MATCHER, ('legacy', )),
- 'version': (VERSION_MATCHER, ('legacy', )),
- 'summary': (SUMMARY_MATCHER, ('legacy', )),
- 'dynamic': (FIELDNAME_MATCHER, ('legacy', )),
- }
-
- __slots__ = ('_legacy', '_data', 'scheme')
-
- def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
- if [path, fileobj, mapping].count(None) < 2:
- raise TypeError('path, fileobj and mapping are exclusive')
- self._legacy = None
- self._data = None
- self.scheme = scheme
- # import pdb; pdb.set_trace()
- if mapping is not None:
- try:
- self._validate_mapping(mapping, scheme)
- self._data = mapping
- except MetadataUnrecognizedVersionError:
- self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme)
- self.validate()
- else:
- data = None
- if path:
- with open(path, 'rb') as f:
- data = f.read()
- elif fileobj:
- data = fileobj.read()
- if data is None:
- # Initialised with no args - to be added
- self._data = {
- 'metadata_version': self.METADATA_VERSION,
- 'generator': self.GENERATOR,
- }
- else:
- if not isinstance(data, text_type):
- data = data.decode('utf-8')
- try:
- self._data = json.loads(data)
- self._validate_mapping(self._data, scheme)
- except ValueError:
- # Note: MetadataUnrecognizedVersionError does not
- # inherit from ValueError (it's a DistlibException,
- # which should not inherit from ValueError).
- # The ValueError comes from the json.load - if that
- # succeeds and we get a validation error, we want
- # that to propagate
- self._legacy = LegacyMetadata(fileobj=StringIO(data), scheme=scheme)
- self.validate()
-
- common_keys = set(('name', 'version', 'license', 'keywords', 'summary'))
-
- none_list = (None, list)
- none_dict = (None, dict)
-
- mapped_keys = {
- 'run_requires': ('Requires-Dist', list),
- 'build_requires': ('Setup-Requires-Dist', list),
- 'dev_requires': none_list,
- 'test_requires': none_list,
- 'meta_requires': none_list,
- 'extras': ('Provides-Extra', list),
- 'modules': none_list,
- 'namespaces': none_list,
- 'exports': none_dict,
- 'commands': none_dict,
- 'classifiers': ('Classifier', list),
- 'source_url': ('Download-URL', None),
- 'metadata_version': ('Metadata-Version', None),
- }
-
- del none_list, none_dict
-
- def __getattribute__(self, key):
- common = object.__getattribute__(self, 'common_keys')
- mapped = object.__getattribute__(self, 'mapped_keys')
- if key in mapped:
- lk, maker = mapped[key]
- if self._legacy:
- if lk is None:
- result = None if maker is None else maker()
- else:
- result = self._legacy.get(lk)
- else:
- value = None if maker is None else maker()
- if key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
- result = self._data.get(key, value)
- else:
- # special cases for PEP 459
- sentinel = object()
- result = sentinel
- d = self._data.get('extensions')
- if d:
- if key == 'commands':
- result = d.get('python.commands', value)
- elif key == 'classifiers':
- d = d.get('python.details')
- if d:
- result = d.get(key, value)
- else:
- d = d.get('python.exports')
- if not d:
- d = self._data.get('python.exports')
- if d:
- result = d.get(key, value)
- if result is sentinel:
- result = value
- elif key not in common:
- result = object.__getattribute__(self, key)
- elif self._legacy:
- result = self._legacy.get(key)
- else:
- result = self._data.get(key)
- return result
-
- def _validate_value(self, key, value, scheme=None):
- if key in self.SYNTAX_VALIDATORS:
- pattern, exclusions = self.SYNTAX_VALIDATORS[key]
- if (scheme or self.scheme) not in exclusions:
- m = pattern.match(value)
- if not m:
- raise MetadataInvalidError("'%s' is an invalid value for "
- "the '%s' property" % (value, key))
-
- def __setattr__(self, key, value):
- self._validate_value(key, value)
- common = object.__getattribute__(self, 'common_keys')
- mapped = object.__getattribute__(self, 'mapped_keys')
- if key in mapped:
- lk, _ = mapped[key]
- if self._legacy:
- if lk is None:
- raise NotImplementedError
- self._legacy[lk] = value
- elif key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
- self._data[key] = value
- else:
- # special cases for PEP 459
- d = self._data.setdefault('extensions', {})
- if key == 'commands':
- d['python.commands'] = value
- elif key == 'classifiers':
- d = d.setdefault('python.details', {})
- d[key] = value
- else:
- d = d.setdefault('python.exports', {})
- d[key] = value
- elif key not in common:
- object.__setattr__(self, key, value)
- else:
- if key == 'keywords':
- if isinstance(value, string_types):
- value = value.strip()
- if value:
- value = value.split()
- else:
- value = []
- if self._legacy:
- self._legacy[key] = value
- else:
- self._data[key] = value
-
- @property
- def name_and_version(self):
- return _get_name_and_version(self.name, self.version, True)
-
- @property
- def provides(self):
- if self._legacy:
- result = self._legacy['Provides-Dist']
- else:
- result = self._data.setdefault('provides', [])
- s = '%s (%s)' % (self.name, self.version)
- if s not in result:
- result.append(s)
- return result
-
- @provides.setter
- def provides(self, value):
- if self._legacy:
- self._legacy['Provides-Dist'] = value
- else:
- self._data['provides'] = value
-
- def get_requirements(self, reqts, extras=None, env=None):
- """
- Base method to get dependencies, given a set of extras
- to satisfy and an optional environment context.
- :param reqts: A list of sometimes-wanted dependencies,
- perhaps dependent on extras and environment.
- :param extras: A list of optional components being requested.
- :param env: An optional environment for marker evaluation.
- """
- if self._legacy:
- result = reqts
- else:
- result = []
- extras = get_extras(extras or [], self.extras)
- for d in reqts:
- if 'extra' not in d and 'environment' not in d:
- # unconditional
- include = True
- else:
- if 'extra' not in d:
- # Not extra-dependent - only environment-dependent
- include = True
- else:
- include = d.get('extra') in extras
- if include:
- # Not excluded because of extras, check environment
- marker = d.get('environment')
- if marker:
- include = interpret(marker, env)
- if include:
- result.extend(d['requires'])
- for key in ('build', 'dev', 'test'):
- e = ':%s:' % key
- if e in extras:
- extras.remove(e)
- # A recursive call, but it should terminate since 'test'
- # has been removed from the extras
- reqts = self._data.get('%s_requires' % key, [])
- result.extend(self.get_requirements(reqts, extras=extras, env=env))
- return result
-
- @property
- def dictionary(self):
- if self._legacy:
- return self._from_legacy()
- return self._data
-
- @property
- def dependencies(self):
- if self._legacy:
- raise NotImplementedError
- else:
- return extract_by_key(self._data, self.DEPENDENCY_KEYS)
-
- @dependencies.setter
- def dependencies(self, value):
- if self._legacy:
- raise NotImplementedError
- else:
- self._data.update(value)
-
- def _validate_mapping(self, mapping, scheme):
- if mapping.get('metadata_version') != self.METADATA_VERSION:
- raise MetadataUnrecognizedVersionError()
- missing = []
- for key, exclusions in self.MANDATORY_KEYS.items():
- if key not in mapping:
- if scheme not in exclusions:
- missing.append(key)
- if missing:
- msg = 'Missing metadata items: %s' % ', '.join(missing)
- raise MetadataMissingError(msg)
- for k, v in mapping.items():
- self._validate_value(k, v, scheme)
-
- def validate(self):
- if self._legacy:
- missing, warnings = self._legacy.check(True)
- if missing or warnings:
- logger.warning('Metadata: missing: %s, warnings: %s', missing, warnings)
- else:
- self._validate_mapping(self._data, self.scheme)
-
- def todict(self):
- if self._legacy:
- return self._legacy.todict(True)
- else:
- result = extract_by_key(self._data, self.INDEX_KEYS)
- return result
-
- def _from_legacy(self):
- assert self._legacy and not self._data
- result = {
- 'metadata_version': self.METADATA_VERSION,
- 'generator': self.GENERATOR,
- }
- lmd = self._legacy.todict(True) # skip missing ones
- for k in ('name', 'version', 'license', 'summary', 'description', 'classifier'):
- if k in lmd:
- if k == 'classifier':
- nk = 'classifiers'
- else:
- nk = k
- result[nk] = lmd[k]
- kw = lmd.get('Keywords', [])
- if kw == ['']:
- kw = []
- result['keywords'] = kw
- keys = (('requires_dist', 'run_requires'), ('setup_requires_dist', 'build_requires'))
- for ok, nk in keys:
- if ok in lmd and lmd[ok]:
- result[nk] = [{'requires': lmd[ok]}]
- result['provides'] = self.provides
- # author = {}
- # maintainer = {}
- return result
-
- LEGACY_MAPPING = {
- 'name': 'Name',
- 'version': 'Version',
- ('extensions', 'python.details', 'license'): 'License',
- 'summary': 'Summary',
- 'description': 'Description',
- ('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page',
- ('extensions', 'python.project', 'contacts', 0, 'name'): 'Author',
- ('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email',
- 'source_url': 'Download-URL',
- ('extensions', 'python.details', 'classifiers'): 'Classifier',
- }
-
- def _to_legacy(self):
-
- def process_entries(entries):
- reqts = set()
- for e in entries:
- extra = e.get('extra')
- env = e.get('environment')
- rlist = e['requires']
- for r in rlist:
- if not env and not extra:
- reqts.add(r)
- else:
- marker = ''
- if extra:
- marker = 'extra == "%s"' % extra
- if env:
- if marker:
- marker = '(%s) and %s' % (env, marker)
- else:
- marker = env
- reqts.add(';'.join((r, marker)))
- return reqts
-
- assert self._data and not self._legacy
- result = LegacyMetadata()
- nmd = self._data
- # import pdb; pdb.set_trace()
- for nk, ok in self.LEGACY_MAPPING.items():
- if not isinstance(nk, tuple):
- if nk in nmd:
- result[ok] = nmd[nk]
- else:
- d = nmd
- found = True
- for k in nk:
- try:
- d = d[k]
- except (KeyError, IndexError):
- found = False
- break
- if found:
- result[ok] = d
- r1 = process_entries(self.run_requires + self.meta_requires)
- r2 = process_entries(self.build_requires + self.dev_requires)
- if self.extras:
- result['Provides-Extra'] = sorted(self.extras)
- result['Requires-Dist'] = sorted(r1)
- result['Setup-Requires-Dist'] = sorted(r2)
- # TODO: any other fields wanted
- return result
-
- def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True):
- if [path, fileobj].count(None) != 1:
- raise ValueError('Exactly one of path and fileobj is needed')
- self.validate()
- if legacy:
- if self._legacy:
- legacy_md = self._legacy
- else:
- legacy_md = self._to_legacy()
- if path:
- legacy_md.write(path, skip_unknown=skip_unknown)
- else:
- legacy_md.write_file(fileobj, skip_unknown=skip_unknown)
- else:
- if self._legacy:
- d = self._from_legacy()
- else:
- d = self._data
- if fileobj:
- json.dump(d, fileobj, ensure_ascii=True, indent=2, sort_keys=True)
- else:
- with codecs.open(path, 'w', 'utf-8') as f:
- json.dump(d, f, ensure_ascii=True, indent=2, sort_keys=True)
-
- def add_requirements(self, requirements):
- if self._legacy:
- self._legacy.add_requirements(requirements)
- else:
- run_requires = self._data.setdefault('run_requires', [])
- always = None
- for entry in run_requires:
- if 'environment' not in entry and 'extra' not in entry:
- always = entry
- break
- if always is None:
- always = {'requires': requirements}
- run_requires.insert(0, always)
- else:
- rset = set(always['requires']) | set(requirements)
- always['requires'] = sorted(rset)
-
- def __repr__(self):
- name = self.name or '(no name)'
- version = self.version or 'no version'
- return '<%s %s %s (%s)>' % (self.__class__.__name__, self.metadata_version, name, version)
diff --git a/contrib/python/pip/pip/_vendor/distlib/scripts.py b/contrib/python/pip/pip/_vendor/distlib/scripts.py
index b1fc705b7e6..195dc3f8909 100644
--- a/contrib/python/pip/pip/_vendor/distlib/scripts.py
+++ b/contrib/python/pip/pip/_vendor/distlib/scripts.py
@@ -42,8 +42,8 @@ FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$')
SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*-
import re
import sys
-from %(module)s import %(import_name)s
if __name__ == '__main__':
+ from %(module)s import %(import_name)s
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(%(func)s())
'''
diff --git a/contrib/python/pip/pip/_vendor/distlib/version.py b/contrib/python/pip/pip/_vendor/distlib/version.py
deleted file mode 100644
index d70a96ef51e..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/version.py
+++ /dev/null
@@ -1,750 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012-2023 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""
-Implementation of a flexible versioning scheme providing support for PEP-440,
-setuptools-compatible and semantic versioning.
-"""
-
-import logging
-import re
-
-from .compat import string_types
-from .util import parse_requirement
-
-__all__ = ['NormalizedVersion', 'NormalizedMatcher',
- 'LegacyVersion', 'LegacyMatcher',
- 'SemanticVersion', 'SemanticMatcher',
- 'UnsupportedVersionError', 'get_scheme']
-
-logger = logging.getLogger(__name__)
-
-
-class UnsupportedVersionError(ValueError):
- """This is an unsupported version."""
- pass
-
-
-class Version(object):
- def __init__(self, s):
- self._string = s = s.strip()
- self._parts = parts = self.parse(s)
- assert isinstance(parts, tuple)
- assert len(parts) > 0
-
- def parse(self, s):
- raise NotImplementedError('please implement in a subclass')
-
- def _check_compatible(self, other):
- if type(self) != type(other):
- raise TypeError('cannot compare %r and %r' % (self, other))
-
- def __eq__(self, other):
- self._check_compatible(other)
- return self._parts == other._parts
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __lt__(self, other):
- self._check_compatible(other)
- return self._parts < other._parts
-
- def __gt__(self, other):
- return not (self.__lt__(other) or self.__eq__(other))
-
- def __le__(self, other):
- return self.__lt__(other) or self.__eq__(other)
-
- def __ge__(self, other):
- return self.__gt__(other) or self.__eq__(other)
-
- # See http://docs.python.org/reference/datamodel#object.__hash__
- def __hash__(self):
- return hash(self._parts)
-
- def __repr__(self):
- return "%s('%s')" % (self.__class__.__name__, self._string)
-
- def __str__(self):
- return self._string
-
- @property
- def is_prerelease(self):
- raise NotImplementedError('Please implement in subclasses.')
-
-
-class Matcher(object):
- version_class = None
-
- # value is either a callable or the name of a method
- _operators = {
- '<': lambda v, c, p: v < c,
- '>': lambda v, c, p: v > c,
- '<=': lambda v, c, p: v == c or v < c,
- '>=': lambda v, c, p: v == c or v > c,
- '==': lambda v, c, p: v == c,
- '===': lambda v, c, p: v == c,
- # by default, compatible => >=.
- '~=': lambda v, c, p: v == c or v > c,
- '!=': lambda v, c, p: v != c,
- }
-
- # this is a method only to support alternative implementations
- # via overriding
- def parse_requirement(self, s):
- return parse_requirement(s)
-
- def __init__(self, s):
- if self.version_class is None:
- raise ValueError('Please specify a version class')
- self._string = s = s.strip()
- r = self.parse_requirement(s)
- if not r:
- raise ValueError('Not valid: %r' % s)
- self.name = r.name
- self.key = self.name.lower() # for case-insensitive comparisons
- clist = []
- if r.constraints:
- # import pdb; pdb.set_trace()
- for op, s in r.constraints:
- if s.endswith('.*'):
- if op not in ('==', '!='):
- raise ValueError('\'.*\' not allowed for '
- '%r constraints' % op)
- # Could be a partial version (e.g. for '2.*') which
- # won't parse as a version, so keep it as a string
- vn, prefix = s[:-2], True
- # Just to check that vn is a valid version
- self.version_class(vn)
- else:
- # Should parse as a version, so we can create an
- # instance for the comparison
- vn, prefix = self.version_class(s), False
- clist.append((op, vn, prefix))
- self._parts = tuple(clist)
-
- def match(self, version):
- """
- Check if the provided version matches the constraints.
-
- :param version: The version to match against this instance.
- :type version: String or :class:`Version` instance.
- """
- if isinstance(version, string_types):
- version = self.version_class(version)
- for operator, constraint, prefix in self._parts:
- f = self._operators.get(operator)
- if isinstance(f, string_types):
- f = getattr(self, f)
- if not f:
- msg = ('%r not implemented '
- 'for %s' % (operator, self.__class__.__name__))
- raise NotImplementedError(msg)
- if not f(version, constraint, prefix):
- return False
- return True
-
- @property
- def exact_version(self):
- result = None
- if len(self._parts) == 1 and self._parts[0][0] in ('==', '==='):
- result = self._parts[0][1]
- return result
-
- def _check_compatible(self, other):
- if type(self) != type(other) or self.name != other.name:
- raise TypeError('cannot compare %s and %s' % (self, other))
-
- def __eq__(self, other):
- self._check_compatible(other)
- return self.key == other.key and self._parts == other._parts
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- # See http://docs.python.org/reference/datamodel#object.__hash__
- def __hash__(self):
- return hash(self.key) + hash(self._parts)
-
- def __repr__(self):
- return "%s(%r)" % (self.__class__.__name__, self._string)
-
- def __str__(self):
- return self._string
-
-
-PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|alpha|b|beta|c|rc|pre|preview)(\d+)?)?'
- r'(\.(post|r|rev)(\d+)?)?([._-]?(dev)(\d+)?)?'
- r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$', re.I)
-
-
-def _pep_440_key(s):
- s = s.strip()
- m = PEP440_VERSION_RE.match(s)
- if not m:
- raise UnsupportedVersionError('Not a valid version: %s' % s)
- groups = m.groups()
- nums = tuple(int(v) for v in groups[1].split('.'))
- while len(nums) > 1 and nums[-1] == 0:
- nums = nums[:-1]
-
- if not groups[0]:
- epoch = 0
- else:
- epoch = int(groups[0][:-1])
- pre = groups[4:6]
- post = groups[7:9]
- dev = groups[10:12]
- local = groups[13]
- if pre == (None, None):
- pre = ()
- else:
- if pre[1] is None:
- pre = pre[0], 0
- else:
- pre = pre[0], int(pre[1])
- if post == (None, None):
- post = ()
- else:
- if post[1] is None:
- post = post[0], 0
- else:
- post = post[0], int(post[1])
- if dev == (None, None):
- dev = ()
- else:
- if dev[1] is None:
- dev = dev[0], 0
- else:
- dev = dev[0], int(dev[1])
- if local is None:
- local = ()
- else:
- parts = []
- for part in local.split('.'):
- # to ensure that numeric compares as > lexicographic, avoid
- # comparing them directly, but encode a tuple which ensures
- # correct sorting
- if part.isdigit():
- part = (1, int(part))
- else:
- part = (0, part)
- parts.append(part)
- local = tuple(parts)
- if not pre:
- # either before pre-release, or final release and after
- if not post and dev:
- # before pre-release
- pre = ('a', -1) # to sort before a0
- else:
- pre = ('z',) # to sort after all pre-releases
- # now look at the state of post and dev.
- if not post:
- post = ('_',) # sort before 'a'
- if not dev:
- dev = ('final',)
-
- return epoch, nums, pre, post, dev, local
-
-
-_normalized_key = _pep_440_key
-
-
-class NormalizedVersion(Version):
- """A rational version.
-
- Good:
- 1.2 # equivalent to "1.2.0"
- 1.2.0
- 1.2a1
- 1.2.3a2
- 1.2.3b1
- 1.2.3c1
- 1.2.3.4
- TODO: fill this out
-
- Bad:
- 1 # minimum two numbers
- 1.2a # release level must have a release serial
- 1.2.3b
- """
- def parse(self, s):
- result = _normalized_key(s)
- # _normalized_key loses trailing zeroes in the release
- # clause, since that's needed to ensure that X.Y == X.Y.0 == X.Y.0.0
- # However, PEP 440 prefix matching needs it: for example,
- # (~= 1.4.5.0) matches differently to (~= 1.4.5.0.0).
- m = PEP440_VERSION_RE.match(s) # must succeed
- groups = m.groups()
- self._release_clause = tuple(int(v) for v in groups[1].split('.'))
- return result
-
- PREREL_TAGS = set(['a', 'b', 'c', 'rc', 'dev'])
-
- @property
- def is_prerelease(self):
- return any(t[0] in self.PREREL_TAGS for t in self._parts if t)
-
-
-def _match_prefix(x, y):
- x = str(x)
- y = str(y)
- if x == y:
- return True
- if not x.startswith(y):
- return False
- n = len(y)
- return x[n] == '.'
-
-
-class NormalizedMatcher(Matcher):
- version_class = NormalizedVersion
-
- # value is either a callable or the name of a method
- _operators = {
- '~=': '_match_compatible',
- '<': '_match_lt',
- '>': '_match_gt',
- '<=': '_match_le',
- '>=': '_match_ge',
- '==': '_match_eq',
- '===': '_match_arbitrary',
- '!=': '_match_ne',
- }
-
- def _adjust_local(self, version, constraint, prefix):
- if prefix:
- strip_local = '+' not in constraint and version._parts[-1]
- else:
- # both constraint and version are
- # NormalizedVersion instances.
- # If constraint does not have a local component,
- # ensure the version doesn't, either.
- strip_local = not constraint._parts[-1] and version._parts[-1]
- if strip_local:
- s = version._string.split('+', 1)[0]
- version = self.version_class(s)
- return version, constraint
-
- def _match_lt(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- if version >= constraint:
- return False
- release_clause = constraint._release_clause
- pfx = '.'.join([str(i) for i in release_clause])
- return not _match_prefix(version, pfx)
-
- def _match_gt(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- if version <= constraint:
- return False
- release_clause = constraint._release_clause
- pfx = '.'.join([str(i) for i in release_clause])
- return not _match_prefix(version, pfx)
-
- def _match_le(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- return version <= constraint
-
- def _match_ge(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- return version >= constraint
-
- def _match_eq(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- if not prefix:
- result = (version == constraint)
- else:
- result = _match_prefix(version, constraint)
- return result
-
- def _match_arbitrary(self, version, constraint, prefix):
- return str(version) == str(constraint)
-
- def _match_ne(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- if not prefix:
- result = (version != constraint)
- else:
- result = not _match_prefix(version, constraint)
- return result
-
- def _match_compatible(self, version, constraint, prefix):
- version, constraint = self._adjust_local(version, constraint, prefix)
- if version == constraint:
- return True
- if version < constraint:
- return False
-# if not prefix:
-# return True
- release_clause = constraint._release_clause
- if len(release_clause) > 1:
- release_clause = release_clause[:-1]
- pfx = '.'.join([str(i) for i in release_clause])
- return _match_prefix(version, pfx)
-
-
-_REPLACEMENTS = (
- (re.compile('[.+-]$'), ''), # remove trailing puncts
- (re.compile(r'^[.](\d)'), r'0.\1'), # .N -> 0.N at start
- (re.compile('^[.-]'), ''), # remove leading puncts
- (re.compile(r'^\((.*)\)$'), r'\1'), # remove parentheses
- (re.compile(r'^v(ersion)?\s*(\d+)'), r'\2'), # remove leading v(ersion)
- (re.compile(r'^r(ev)?\s*(\d+)'), r'\2'), # remove leading v(ersion)
- (re.compile('[.]{2,}'), '.'), # multiple runs of '.'
- (re.compile(r'\b(alfa|apha)\b'), 'alpha'), # misspelt alpha
- (re.compile(r'\b(pre-alpha|prealpha)\b'),
- 'pre.alpha'), # standardise
- (re.compile(r'\(beta\)$'), 'beta'), # remove parentheses
-)
-
-_SUFFIX_REPLACEMENTS = (
- (re.compile('^[:~._+-]+'), ''), # remove leading puncts
- (re.compile('[,*")([\\]]'), ''), # remove unwanted chars
- (re.compile('[~:+_ -]'), '.'), # replace illegal chars
- (re.compile('[.]{2,}'), '.'), # multiple runs of '.'
- (re.compile(r'\.$'), ''), # trailing '.'
-)
-
-_NUMERIC_PREFIX = re.compile(r'(\d+(\.\d+)*)')
-
-
-def _suggest_semantic_version(s):
- """
- Try to suggest a semantic form for a version for which
- _suggest_normalized_version couldn't come up with anything.
- """
- result = s.strip().lower()
- for pat, repl in _REPLACEMENTS:
- result = pat.sub(repl, result)
- if not result:
- result = '0.0.0'
-
- # Now look for numeric prefix, and separate it out from
- # the rest.
- # import pdb; pdb.set_trace()
- m = _NUMERIC_PREFIX.match(result)
- if not m:
- prefix = '0.0.0'
- suffix = result
- else:
- prefix = m.groups()[0].split('.')
- prefix = [int(i) for i in prefix]
- while len(prefix) < 3:
- prefix.append(0)
- if len(prefix) == 3:
- suffix = result[m.end():]
- else:
- suffix = '.'.join([str(i) for i in prefix[3:]]) + result[m.end():]
- prefix = prefix[:3]
- prefix = '.'.join([str(i) for i in prefix])
- suffix = suffix.strip()
- if suffix:
- # import pdb; pdb.set_trace()
- # massage the suffix.
- for pat, repl in _SUFFIX_REPLACEMENTS:
- suffix = pat.sub(repl, suffix)
-
- if not suffix:
- result = prefix
- else:
- sep = '-' if 'dev' in suffix else '+'
- result = prefix + sep + suffix
- if not is_semver(result):
- result = None
- return result
-
-
-def _suggest_normalized_version(s):
- """Suggest a normalized version close to the given version string.
-
- If you have a version string that isn't rational (i.e. NormalizedVersion
- doesn't like it) then you might be able to get an equivalent (or close)
- rational version from this function.
-
- This does a number of simple normalizations to the given string, based
- on observation of versions currently in use on PyPI. Given a dump of
- those version during PyCon 2009, 4287 of them:
- - 2312 (53.93%) match NormalizedVersion without change
- with the automatic suggestion
- - 3474 (81.04%) match when using this suggestion method
-
- @param s {str} An irrational version string.
- @returns A rational version string, or None, if couldn't determine one.
- """
- try:
- _normalized_key(s)
- return s # already rational
- except UnsupportedVersionError:
- pass
-
- rs = s.lower()
-
- # part of this could use maketrans
- for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'),
- ('beta', 'b'), ('rc', 'c'), ('-final', ''),
- ('-pre', 'c'),
- ('-release', ''), ('.release', ''), ('-stable', ''),
- ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''),
- ('final', '')):
- rs = rs.replace(orig, repl)
-
- # if something ends with dev or pre, we add a 0
- rs = re.sub(r"pre$", r"pre0", rs)
- rs = re.sub(r"dev$", r"dev0", rs)
-
- # if we have something like "b-2" or "a.2" at the end of the
- # version, that is probably beta, alpha, etc
- # let's remove the dash or dot
- rs = re.sub(r"([abc]|rc)[\-\.](\d+)$", r"\1\2", rs)
-
- # 1.0-dev-r371 -> 1.0.dev371
- # 0.1-dev-r79 -> 0.1.dev79
- rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs)
-
- # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1
- rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs)
-
- # Clean: v0.3, v1.0
- if rs.startswith('v'):
- rs = rs[1:]
-
- # Clean leading '0's on numbers.
- # TODO: unintended side-effect on, e.g., "2003.05.09"
- # PyPI stats: 77 (~2%) better
- rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
-
- # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers
- # zero.
- # PyPI stats: 245 (7.56%) better
- rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs)
-
- # the 'dev-rNNN' tag is a dev tag
- rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs)
-
- # clean the - when used as a pre delimiter
- rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs)
-
- # a terminal "dev" or "devel" can be changed into ".dev0"
- rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs)
-
- # a terminal "dev" can be changed into ".dev0"
- rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs)
-
- # a terminal "final" or "stable" can be removed
- rs = re.sub(r"(final|stable)$", "", rs)
-
- # The 'r' and the '-' tags are post release tags
- # 0.4a1.r10 -> 0.4a1.post10
- # 0.9.33-17222 -> 0.9.33.post17222
- # 0.9.33-r17222 -> 0.9.33.post17222
- rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs)
-
- # Clean 'r' instead of 'dev' usage:
- # 0.9.33+r17222 -> 0.9.33.dev17222
- # 1.0dev123 -> 1.0.dev123
- # 1.0.git123 -> 1.0.dev123
- # 1.0.bzr123 -> 1.0.dev123
- # 0.1a0dev.123 -> 0.1a0.dev123
- # PyPI stats: ~150 (~4%) better
- rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs)
-
- # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage:
- # 0.2.pre1 -> 0.2c1
- # 0.2-c1 -> 0.2c1
- # 1.0preview123 -> 1.0c123
- # PyPI stats: ~21 (0.62%) better
- rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
-
- # Tcl/Tk uses "px" for their post release markers
- rs = re.sub(r"p(\d+)$", r".post\1", rs)
-
- try:
- _normalized_key(rs)
- except UnsupportedVersionError:
- rs = None
- return rs
-
-#
-# Legacy version processing (distribute-compatible)
-#
-
-
-_VERSION_PART = re.compile(r'([a-z]+|\d+|[\.-])', re.I)
-_VERSION_REPLACE = {
- 'pre': 'c',
- 'preview': 'c',
- '-': 'final-',
- 'rc': 'c',
- 'dev': '@',
- '': None,
- '.': None,
-}
-
-
-def _legacy_key(s):
- def get_parts(s):
- result = []
- for p in _VERSION_PART.split(s.lower()):
- p = _VERSION_REPLACE.get(p, p)
- if p:
- if '0' <= p[:1] <= '9':
- p = p.zfill(8)
- else:
- p = '*' + p
- result.append(p)
- result.append('*final')
- return result
-
- result = []
- for p in get_parts(s):
- if p.startswith('*'):
- if p < '*final':
- while result and result[-1] == '*final-':
- result.pop()
- while result and result[-1] == '00000000':
- result.pop()
- result.append(p)
- return tuple(result)
-
-
-class LegacyVersion(Version):
- def parse(self, s):
- return _legacy_key(s)
-
- @property
- def is_prerelease(self):
- result = False
- for x in self._parts:
- if (isinstance(x, string_types) and x.startswith('*') and x < '*final'):
- result = True
- break
- return result
-
-
-class LegacyMatcher(Matcher):
- version_class = LegacyVersion
-
- _operators = dict(Matcher._operators)
- _operators['~='] = '_match_compatible'
-
- numeric_re = re.compile(r'^(\d+(\.\d+)*)')
-
- def _match_compatible(self, version, constraint, prefix):
- if version < constraint:
- return False
- m = self.numeric_re.match(str(constraint))
- if not m:
- logger.warning('Cannot compute compatible match for version %s '
- ' and constraint %s', version, constraint)
- return True
- s = m.groups()[0]
- if '.' in s:
- s = s.rsplit('.', 1)[0]
- return _match_prefix(version, s)
-
-#
-# Semantic versioning
-#
-
-
-_SEMVER_RE = re.compile(r'^(\d+)\.(\d+)\.(\d+)'
- r'(-[a-z0-9]+(\.[a-z0-9-]+)*)?'
- r'(\+[a-z0-9]+(\.[a-z0-9-]+)*)?$', re.I)
-
-
-def is_semver(s):
- return _SEMVER_RE.match(s)
-
-
-def _semantic_key(s):
- def make_tuple(s, absent):
- if s is None:
- result = (absent,)
- else:
- parts = s[1:].split('.')
- # We can't compare ints and strings on Python 3, so fudge it
- # by zero-filling numeric values so simulate a numeric comparison
- result = tuple([p.zfill(8) if p.isdigit() else p for p in parts])
- return result
-
- m = is_semver(s)
- if not m:
- raise UnsupportedVersionError(s)
- groups = m.groups()
- major, minor, patch = [int(i) for i in groups[:3]]
- # choose the '|' and '*' so that versions sort correctly
- pre, build = make_tuple(groups[3], '|'), make_tuple(groups[5], '*')
- return (major, minor, patch), pre, build
-
-
-class SemanticVersion(Version):
- def parse(self, s):
- return _semantic_key(s)
-
- @property
- def is_prerelease(self):
- return self._parts[1][0] != '|'
-
-
-class SemanticMatcher(Matcher):
- version_class = SemanticVersion
-
-
-class VersionScheme(object):
- def __init__(self, key, matcher, suggester=None):
- self.key = key
- self.matcher = matcher
- self.suggester = suggester
-
- def is_valid_version(self, s):
- try:
- self.matcher.version_class(s)
- result = True
- except UnsupportedVersionError:
- result = False
- return result
-
- def is_valid_matcher(self, s):
- try:
- self.matcher(s)
- result = True
- except UnsupportedVersionError:
- result = False
- return result
-
- def is_valid_constraint_list(self, s):
- """
- Used for processing some metadata fields
- """
- # See issue #140. Be tolerant of a single trailing comma.
- if s.endswith(','):
- s = s[:-1]
- return self.is_valid_matcher('dummy_name (%s)' % s)
-
- def suggest(self, s):
- if self.suggester is None:
- result = None
- else:
- result = self.suggester(s)
- return result
-
-
-_SCHEMES = {
- 'normalized': VersionScheme(_normalized_key, NormalizedMatcher,
- _suggest_normalized_version),
- 'legacy': VersionScheme(_legacy_key, LegacyMatcher, lambda self, s: s),
- 'semantic': VersionScheme(_semantic_key, SemanticMatcher,
- _suggest_semantic_version),
-}
-
-_SCHEMES['default'] = _SCHEMES['normalized']
-
-
-def get_scheme(name):
- if name not in _SCHEMES:
- raise ValueError('unknown scheme name: %r' % name)
- return _SCHEMES[name]
diff --git a/contrib/python/pip/pip/_vendor/distlib/wheel.py b/contrib/python/pip/pip/_vendor/distlib/wheel.py
deleted file mode 100644
index 62ab10fb3ad..00000000000
--- a/contrib/python/pip/pip/_vendor/distlib/wheel.py
+++ /dev/null
@@ -1,1100 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013-2023 Vinay Sajip.
-# Licensed to the Python Software Foundation under a contributor agreement.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-from __future__ import unicode_literals
-
-import base64
-import codecs
-import datetime
-from email import message_from_file
-import hashlib
-import json
-import logging
-import os
-import posixpath
-import re
-import shutil
-import sys
-import tempfile
-import zipfile
-
-from . import __version__, DistlibException
-from .compat import sysconfig, ZipFile, fsdecode, text_type, filter
-from .database import InstalledDistribution
-from .metadata import Metadata, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME
-from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, cached_property, get_cache_base,
- read_exports, tempdir, get_platform)
-from .version import NormalizedVersion, UnsupportedVersionError
-
-logger = logging.getLogger(__name__)
-
-cache = None # created when needed
-
-if hasattr(sys, 'pypy_version_info'): # pragma: no cover
- IMP_PREFIX = 'pp'
-elif sys.platform.startswith('java'): # pragma: no cover
- IMP_PREFIX = 'jy'
-elif sys.platform == 'cli': # pragma: no cover
- IMP_PREFIX = 'ip'
-else:
- IMP_PREFIX = 'cp'
-
-VER_SUFFIX = sysconfig.get_config_var('py_version_nodot')
-if not VER_SUFFIX: # pragma: no cover
- VER_SUFFIX = '%s%s' % sys.version_info[:2]
-PYVER = 'py' + VER_SUFFIX
-IMPVER = IMP_PREFIX + VER_SUFFIX
-
-ARCH = get_platform().replace('-', '_').replace('.', '_')
-
-ABI = sysconfig.get_config_var('SOABI')
-if ABI and ABI.startswith('cpython-'):
- ABI = ABI.replace('cpython-', 'cp').split('-')[0]
-else:
-
- def _derive_abi():
- parts = ['cp', VER_SUFFIX]
- if sysconfig.get_config_var('Py_DEBUG'):
- parts.append('d')
- if IMP_PREFIX == 'cp':
- vi = sys.version_info[:2]
- if vi < (3, 8):
- wpm = sysconfig.get_config_var('WITH_PYMALLOC')
- if wpm is None:
- wpm = True
- if wpm:
- parts.append('m')
- if vi < (3, 3):
- us = sysconfig.get_config_var('Py_UNICODE_SIZE')
- if us == 4 or (us is None and sys.maxunicode == 0x10FFFF):
- parts.append('u')
- return ''.join(parts)
-
- ABI = _derive_abi()
- del _derive_abi
-
-FILENAME_RE = re.compile(
- r'''
-(?P<nm>[^-]+)
--(?P<vn>\d+[^-]*)
-(-(?P<bn>\d+[^-]*))?
--(?P<py>\w+\d+(\.\w+\d+)*)
--(?P<bi>\w+)
--(?P<ar>\w+(\.\w+)*)
-\.whl$
-''', re.IGNORECASE | re.VERBOSE)
-
-NAME_VERSION_RE = re.compile(r'''
-(?P<nm>[^-]+)
--(?P<vn>\d+[^-]*)
-(-(?P<bn>\d+[^-]*))?$
-''', re.IGNORECASE | re.VERBOSE)
-
-SHEBANG_RE = re.compile(br'\s*#![^\r\n]*')
-SHEBANG_DETAIL_RE = re.compile(br'^(\s*#!("[^"]+"|\S+))\s+(.*)$')
-SHEBANG_PYTHON = b'#!python'
-SHEBANG_PYTHONW = b'#!pythonw'
-
-if os.sep == '/':
- to_posix = lambda o: o
-else:
- to_posix = lambda o: o.replace(os.sep, '/')
-
-if sys.version_info[0] < 3:
- import imp
-else:
- imp = None
- import importlib.machinery
- import importlib.util
-
-
-def _get_suffixes():
- if imp:
- return [s[0] for s in imp.get_suffixes()]
- else:
- return importlib.machinery.EXTENSION_SUFFIXES
-
-
-def _load_dynamic(name, path):
- # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
- if imp:
- return imp.load_dynamic(name, path)
- else:
- spec = importlib.util.spec_from_file_location(name, path)
- module = importlib.util.module_from_spec(spec)
- sys.modules[name] = module
- spec.loader.exec_module(module)
- return module
-
-
-class Mounter(object):
-
- def __init__(self):
- self.impure_wheels = {}
- self.libs = {}
-
- def add(self, pathname, extensions):
- self.impure_wheels[pathname] = extensions
- self.libs.update(extensions)
-
- def remove(self, pathname):
- extensions = self.impure_wheels.pop(pathname)
- for k, v in extensions:
- if k in self.libs:
- del self.libs[k]
-
- def find_module(self, fullname, path=None):
- if fullname in self.libs:
- result = self
- else:
- result = None
- return result
-
- def load_module(self, fullname):
- if fullname in sys.modules:
- result = sys.modules[fullname]
- else:
- if fullname not in self.libs:
- raise ImportError('unable to find extension for %s' % fullname)
- result = _load_dynamic(fullname, self.libs[fullname])
- result.__loader__ = self
- parts = fullname.rsplit('.', 1)
- if len(parts) > 1:
- result.__package__ = parts[0]
- return result
-
-
-_hook = Mounter()
-
-
-class Wheel(object):
- """
- Class to build and install from Wheel files (PEP 427).
- """
-
- wheel_version = (1, 1)
- hash_kind = 'sha256'
-
- def __init__(self, filename=None, sign=False, verify=False):
- """
- Initialise an instance using a (valid) filename.
- """
- self.sign = sign
- self.should_verify = verify
- self.buildver = ''
- self.pyver = [PYVER]
- self.abi = ['none']
- self.arch = ['any']
- self.dirname = os.getcwd()
- if filename is None:
- self.name = 'dummy'
- self.version = '0.1'
- self._filename = self.filename
- else:
- m = NAME_VERSION_RE.match(filename)
- if m:
- info = m.groupdict('')
- self.name = info['nm']
- # Reinstate the local version separator
- self.version = info['vn'].replace('_', '-')
- self.buildver = info['bn']
- self._filename = self.filename
- else:
- dirname, filename = os.path.split(filename)
- m = FILENAME_RE.match(filename)
- if not m:
- raise DistlibException('Invalid name or '
- 'filename: %r' % filename)
- if dirname:
- self.dirname = os.path.abspath(dirname)
- self._filename = filename
- info = m.groupdict('')
- self.name = info['nm']
- self.version = info['vn']
- self.buildver = info['bn']
- self.pyver = info['py'].split('.')
- self.abi = info['bi'].split('.')
- self.arch = info['ar'].split('.')
-
- @property
- def filename(self):
- """
- Build and return a filename from the various components.
- """
- if self.buildver:
- buildver = '-' + self.buildver
- else:
- buildver = ''
- pyver = '.'.join(self.pyver)
- abi = '.'.join(self.abi)
- arch = '.'.join(self.arch)
- # replace - with _ as a local version separator
- version = self.version.replace('-', '_')
- return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, pyver, abi, arch)
-
- @property
- def exists(self):
- path = os.path.join(self.dirname, self.filename)
- return os.path.isfile(path)
-
- @property
- def tags(self):
- for pyver in self.pyver:
- for abi in self.abi:
- for arch in self.arch:
- yield pyver, abi, arch
-
- @cached_property
- def metadata(self):
- pathname = os.path.join(self.dirname, self.filename)
- name_ver = '%s-%s' % (self.name, self.version)
- info_dir = '%s.dist-info' % name_ver
- wrapper = codecs.getreader('utf-8')
- with ZipFile(pathname, 'r') as zf:
- self.get_wheel_metadata(zf)
- # wv = wheel_metadata['Wheel-Version'].split('.', 1)
- # file_version = tuple([int(i) for i in wv])
- # if file_version < (1, 1):
- # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME,
- # LEGACY_METADATA_FILENAME]
- # else:
- # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME]
- fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
- result = None
- for fn in fns:
- try:
- metadata_filename = posixpath.join(info_dir, fn)
- with zf.open(metadata_filename) as bf:
- wf = wrapper(bf)
- result = Metadata(fileobj=wf)
- if result:
- break
- except KeyError:
- pass
- if not result:
- raise ValueError('Invalid wheel, because metadata is '
- 'missing: looked in %s' % ', '.join(fns))
- return result
-
- def get_wheel_metadata(self, zf):
- name_ver = '%s-%s' % (self.name, self.version)
- info_dir = '%s.dist-info' % name_ver
- metadata_filename = posixpath.join(info_dir, 'WHEEL')
- with zf.open(metadata_filename) as bf:
- wf = codecs.getreader('utf-8')(bf)
- message = message_from_file(wf)
- return dict(message)
-
- @cached_property
- def info(self):
- pathname = os.path.join(self.dirname, self.filename)
- with ZipFile(pathname, 'r') as zf:
- result = self.get_wheel_metadata(zf)
- return result
-
- def process_shebang(self, data):
- m = SHEBANG_RE.match(data)
- if m:
- end = m.end()
- shebang, data_after_shebang = data[:end], data[end:]
- # Preserve any arguments after the interpreter
- if b'pythonw' in shebang.lower():
- shebang_python = SHEBANG_PYTHONW
- else:
- shebang_python = SHEBANG_PYTHON
- m = SHEBANG_DETAIL_RE.match(shebang)
- if m:
- args = b' ' + m.groups()[-1]
- else:
- args = b''
- shebang = shebang_python + args
- data = shebang + data_after_shebang
- else:
- cr = data.find(b'\r')
- lf = data.find(b'\n')
- if cr < 0 or cr > lf:
- term = b'\n'
- else:
- if data[cr:cr + 2] == b'\r\n':
- term = b'\r\n'
- else:
- term = b'\r'
- data = SHEBANG_PYTHON + term + data
- return data
-
- def get_hash(self, data, hash_kind=None):
- if hash_kind is None:
- hash_kind = self.hash_kind
- try:
- hasher = getattr(hashlib, hash_kind)
- except AttributeError:
- raise DistlibException('Unsupported hash algorithm: %r' % hash_kind)
- result = hasher(data).digest()
- result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii')
- return hash_kind, result
-
- def write_record(self, records, record_path, archive_record_path):
- records = list(records) # make a copy, as mutated
- records.append((archive_record_path, '', ''))
- with CSVWriter(record_path) as writer:
- for row in records:
- writer.writerow(row)
-
- def write_records(self, info, libdir, archive_paths):
- records = []
- distinfo, info_dir = info
- # hasher = getattr(hashlib, self.hash_kind)
- for ap, p in archive_paths:
- with open(p, 'rb') as f:
- data = f.read()
- digest = '%s=%s' % self.get_hash(data)
- size = os.path.getsize(p)
- records.append((ap, digest, size))
-
- p = os.path.join(distinfo, 'RECORD')
- ap = to_posix(os.path.join(info_dir, 'RECORD'))
- self.write_record(records, p, ap)
- archive_paths.append((ap, p))
-
- def build_zip(self, pathname, archive_paths):
- with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf:
- for ap, p in archive_paths:
- logger.debug('Wrote %s to %s in wheel', p, ap)
- zf.write(p, ap)
-
- def build(self, paths, tags=None, wheel_version=None):
- """
- Build a wheel from files in specified paths, and use any specified tags
- when determining the name of the wheel.
- """
- if tags is None:
- tags = {}
-
- libkey = list(filter(lambda o: o in paths, ('purelib', 'platlib')))[0]
- if libkey == 'platlib':
- is_pure = 'false'
- default_pyver = [IMPVER]
- default_abi = [ABI]
- default_arch = [ARCH]
- else:
- is_pure = 'true'
- default_pyver = [PYVER]
- default_abi = ['none']
- default_arch = ['any']
-
- self.pyver = tags.get('pyver', default_pyver)
- self.abi = tags.get('abi', default_abi)
- self.arch = tags.get('arch', default_arch)
-
- libdir = paths[libkey]
-
- name_ver = '%s-%s' % (self.name, self.version)
- data_dir = '%s.data' % name_ver
- info_dir = '%s.dist-info' % name_ver
-
- archive_paths = []
-
- # First, stuff which is not in site-packages
- for key in ('data', 'headers', 'scripts'):
- if key not in paths:
- continue
- path = paths[key]
- if os.path.isdir(path):
- for root, dirs, files in os.walk(path):
- for fn in files:
- p = fsdecode(os.path.join(root, fn))
- rp = os.path.relpath(p, path)
- ap = to_posix(os.path.join(data_dir, key, rp))
- archive_paths.append((ap, p))
- if key == 'scripts' and not p.endswith('.exe'):
- with open(p, 'rb') as f:
- data = f.read()
- data = self.process_shebang(data)
- with open(p, 'wb') as f:
- f.write(data)
-
- # Now, stuff which is in site-packages, other than the
- # distinfo stuff.
- path = libdir
- distinfo = None
- for root, dirs, files in os.walk(path):
- if root == path:
- # At the top level only, save distinfo for later
- # and skip it for now
- for i, dn in enumerate(dirs):
- dn = fsdecode(dn)
- if dn.endswith('.dist-info'):
- distinfo = os.path.join(root, dn)
- del dirs[i]
- break
- assert distinfo, '.dist-info directory expected, not found'
-
- for fn in files:
- # comment out next suite to leave .pyc files in
- if fsdecode(fn).endswith(('.pyc', '.pyo')):
- continue
- p = os.path.join(root, fn)
- rp = to_posix(os.path.relpath(p, path))
- archive_paths.append((rp, p))
-
- # Now distinfo. Assumed to be flat, i.e. os.listdir is enough.
- files = os.listdir(distinfo)
- for fn in files:
- if fn not in ('RECORD', 'INSTALLER', 'SHARED', 'WHEEL'):
- p = fsdecode(os.path.join(distinfo, fn))
- ap = to_posix(os.path.join(info_dir, fn))
- archive_paths.append((ap, p))
-
- wheel_metadata = [
- 'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version),
- 'Generator: distlib %s' % __version__,
- 'Root-Is-Purelib: %s' % is_pure,
- ]
- for pyver, abi, arch in self.tags:
- wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch))
- p = os.path.join(distinfo, 'WHEEL')
- with open(p, 'w') as f:
- f.write('\n'.join(wheel_metadata))
- ap = to_posix(os.path.join(info_dir, 'WHEEL'))
- archive_paths.append((ap, p))
-
- # sort the entries by archive path. Not needed by any spec, but it
- # keeps the archive listing and RECORD tidier than they would otherwise
- # be. Use the number of path segments to keep directory entries together,
- # and keep the dist-info stuff at the end.
- def sorter(t):
- ap = t[0]
- n = ap.count('/')
- if '.dist-info' in ap:
- n += 10000
- return (n, ap)
-
- archive_paths = sorted(archive_paths, key=sorter)
-
- # Now, at last, RECORD.
- # Paths in here are archive paths - nothing else makes sense.
- self.write_records((distinfo, info_dir), libdir, archive_paths)
- # Now, ready to build the zip file
- pathname = os.path.join(self.dirname, self.filename)
- self.build_zip(pathname, archive_paths)
- return pathname
-
- def skip_entry(self, arcname):
- """
- Determine whether an archive entry should be skipped when verifying
- or installing.
- """
- # The signature file won't be in RECORD,
- # and we don't currently don't do anything with it
- # We also skip directories, as they won't be in RECORD
- # either. See:
- #
- # https://github.com/pypa/wheel/issues/294
- # https://github.com/pypa/wheel/issues/287
- # https://github.com/pypa/wheel/pull/289
- #
- return arcname.endswith(('/', '/RECORD.jws'))
-
- def install(self, paths, maker, **kwargs):
- """
- Install a wheel to the specified paths. If kwarg ``warner`` is
- specified, it should be a callable, which will be called with two
- tuples indicating the wheel version of this software and the wheel
- version in the file, if there is a discrepancy in the versions.
- This can be used to issue any warnings to raise any exceptions.
- If kwarg ``lib_only`` is True, only the purelib/platlib files are
- installed, and the headers, scripts, data and dist-info metadata are
- not written. If kwarg ``bytecode_hashed_invalidation`` is True, written
- bytecode will try to use file-hash based invalidation (PEP-552) on
- supported interpreter versions (CPython 3.7+).
-
- The return value is a :class:`InstalledDistribution` instance unless
- ``options.lib_only`` is True, in which case the return value is ``None``.
- """
-
- dry_run = maker.dry_run
- warner = kwargs.get('warner')
- lib_only = kwargs.get('lib_only', False)
- bc_hashed_invalidation = kwargs.get('bytecode_hashed_invalidation', False)
-
- pathname = os.path.join(self.dirname, self.filename)
- name_ver = '%s-%s' % (self.name, self.version)
- data_dir = '%s.data' % name_ver
- info_dir = '%s.dist-info' % name_ver
-
- metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME)
- wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
- record_name = posixpath.join(info_dir, 'RECORD')
-
- wrapper = codecs.getreader('utf-8')
-
- with ZipFile(pathname, 'r') as zf:
- with zf.open(wheel_metadata_name) as bwf:
- wf = wrapper(bwf)
- message = message_from_file(wf)
- wv = message['Wheel-Version'].split('.', 1)
- file_version = tuple([int(i) for i in wv])
- if (file_version != self.wheel_version) and warner:
- warner(self.wheel_version, file_version)
-
- if message['Root-Is-Purelib'] == 'true':
- libdir = paths['purelib']
- else:
- libdir = paths['platlib']
-
- records = {}
- with zf.open(record_name) as bf:
- with CSVReader(stream=bf) as reader:
- for row in reader:
- p = row[0]
- records[p] = row
-
- data_pfx = posixpath.join(data_dir, '')
- info_pfx = posixpath.join(info_dir, '')
- script_pfx = posixpath.join(data_dir, 'scripts', '')
-
- # make a new instance rather than a copy of maker's,
- # as we mutate it
- fileop = FileOperator(dry_run=dry_run)
- fileop.record = True # so we can rollback if needed
-
- bc = not sys.dont_write_bytecode # Double negatives. Lovely!
-
- outfiles = [] # for RECORD writing
-
- # for script copying/shebang processing
- workdir = tempfile.mkdtemp()
- # set target dir later
- # we default add_launchers to False, as the
- # Python Launcher should be used instead
- maker.source_dir = workdir
- maker.target_dir = None
- try:
- for zinfo in zf.infolist():
- arcname = zinfo.filename
- if isinstance(arcname, text_type):
- u_arcname = arcname
- else:
- u_arcname = arcname.decode('utf-8')
- if self.skip_entry(u_arcname):
- continue
- row = records[u_arcname]
- if row[2] and str(zinfo.file_size) != row[2]:
- raise DistlibException('size mismatch for '
- '%s' % u_arcname)
- if row[1]:
- kind, value = row[1].split('=', 1)
- with zf.open(arcname) as bf:
- data = bf.read()
- _, digest = self.get_hash(data, kind)
- if digest != value:
- raise DistlibException('digest mismatch for '
- '%s' % arcname)
-
- if lib_only and u_arcname.startswith((info_pfx, data_pfx)):
- logger.debug('lib_only: skipping %s', u_arcname)
- continue
- is_script = (u_arcname.startswith(script_pfx) and not u_arcname.endswith('.exe'))
-
- if u_arcname.startswith(data_pfx):
- _, where, rp = u_arcname.split('/', 2)
- outfile = os.path.join(paths[where], convert_path(rp))
- else:
- # meant for site-packages.
- if u_arcname in (wheel_metadata_name, record_name):
- continue
- outfile = os.path.join(libdir, convert_path(u_arcname))
- if not is_script:
- with zf.open(arcname) as bf:
- fileop.copy_stream(bf, outfile)
- # Issue #147: permission bits aren't preserved. Using
- # zf.extract(zinfo, libdir) should have worked, but didn't,
- # see https://www.thetopsites.net/article/53834422.shtml
- # So ... manually preserve permission bits as given in zinfo
- if os.name == 'posix':
- # just set the normal permission bits
- os.chmod(outfile, (zinfo.external_attr >> 16) & 0x1FF)
- outfiles.append(outfile)
- # Double check the digest of the written file
- if not dry_run and row[1]:
- with open(outfile, 'rb') as bf:
- data = bf.read()
- _, newdigest = self.get_hash(data, kind)
- if newdigest != digest:
- raise DistlibException('digest mismatch '
- 'on write for '
- '%s' % outfile)
- if bc and outfile.endswith('.py'):
- try:
- pyc = fileop.byte_compile(outfile, hashed_invalidation=bc_hashed_invalidation)
- outfiles.append(pyc)
- except Exception:
- # Don't give up if byte-compilation fails,
- # but log it and perhaps warn the user
- logger.warning('Byte-compilation failed', exc_info=True)
- else:
- fn = os.path.basename(convert_path(arcname))
- workname = os.path.join(workdir, fn)
- with zf.open(arcname) as bf:
- fileop.copy_stream(bf, workname)
-
- dn, fn = os.path.split(outfile)
- maker.target_dir = dn
- filenames = maker.make(fn)
- fileop.set_executable_mode(filenames)
- outfiles.extend(filenames)
-
- if lib_only:
- logger.debug('lib_only: returning None')
- dist = None
- else:
- # Generate scripts
-
- # Try to get pydist.json so we can see if there are
- # any commands to generate. If this fails (e.g. because
- # of a legacy wheel), log a warning but don't give up.
- commands = None
- file_version = self.info['Wheel-Version']
- if file_version == '1.0':
- # Use legacy info
- ep = posixpath.join(info_dir, 'entry_points.txt')
- try:
- with zf.open(ep) as bwf:
- epdata = read_exports(bwf)
- commands = {}
- for key in ('console', 'gui'):
- k = '%s_scripts' % key
- if k in epdata:
- commands['wrap_%s' % key] = d = {}
- for v in epdata[k].values():
- s = '%s:%s' % (v.prefix, v.suffix)
- if v.flags:
- s += ' [%s]' % ','.join(v.flags)
- d[v.name] = s
- except Exception:
- logger.warning('Unable to read legacy script '
- 'metadata, so cannot generate '
- 'scripts')
- else:
- try:
- with zf.open(metadata_name) as bwf:
- wf = wrapper(bwf)
- commands = json.load(wf).get('extensions')
- if commands:
- commands = commands.get('python.commands')
- except Exception:
- logger.warning('Unable to read JSON metadata, so '
- 'cannot generate scripts')
- if commands:
- console_scripts = commands.get('wrap_console', {})
- gui_scripts = commands.get('wrap_gui', {})
- if console_scripts or gui_scripts:
- script_dir = paths.get('scripts', '')
- if not os.path.isdir(script_dir):
- raise ValueError('Valid script path not '
- 'specified')
- maker.target_dir = script_dir
- for k, v in console_scripts.items():
- script = '%s = %s' % (k, v)
- filenames = maker.make(script)
- fileop.set_executable_mode(filenames)
-
- if gui_scripts:
- options = {'gui': True}
- for k, v in gui_scripts.items():
- script = '%s = %s' % (k, v)
- filenames = maker.make(script, options)
- fileop.set_executable_mode(filenames)
-
- p = os.path.join(libdir, info_dir)
- dist = InstalledDistribution(p)
-
- # Write SHARED
- paths = dict(paths) # don't change passed in dict
- del paths['purelib']
- del paths['platlib']
- paths['lib'] = libdir
- p = dist.write_shared_locations(paths, dry_run)
- if p:
- outfiles.append(p)
-
- # Write RECORD
- dist.write_installed_files(outfiles, paths['prefix'], dry_run)
- return dist
- except Exception: # pragma: no cover
- logger.exception('installation failed.')
- fileop.rollback()
- raise
- finally:
- shutil.rmtree(workdir)
-
- def _get_dylib_cache(self):
- global cache
- if cache is None:
- # Use native string to avoid issues on 2.x: see Python #20140.
- base = os.path.join(get_cache_base(), str('dylib-cache'), '%s.%s' % sys.version_info[:2])
- cache = Cache(base)
- return cache
-
- def _get_extensions(self):
- pathname = os.path.join(self.dirname, self.filename)
- name_ver = '%s-%s' % (self.name, self.version)
- info_dir = '%s.dist-info' % name_ver
- arcname = posixpath.join(info_dir, 'EXTENSIONS')
- wrapper = codecs.getreader('utf-8')
- result = []
- with ZipFile(pathname, 'r') as zf:
- try:
- with zf.open(arcname) as bf:
- wf = wrapper(bf)
- extensions = json.load(wf)
- cache = self._get_dylib_cache()
- prefix = cache.prefix_to_dir(self.filename, use_abspath=False)
- cache_base = os.path.join(cache.base, prefix)
- if not os.path.isdir(cache_base):
- os.makedirs(cache_base)
- for name, relpath in extensions.items():
- dest = os.path.join(cache_base, convert_path(relpath))
- if not os.path.exists(dest):
- extract = True
- else:
- file_time = os.stat(dest).st_mtime
- file_time = datetime.datetime.fromtimestamp(file_time)
- info = zf.getinfo(relpath)
- wheel_time = datetime.datetime(*info.date_time)
- extract = wheel_time > file_time
- if extract:
- zf.extract(relpath, cache_base)
- result.append((name, dest))
- except KeyError:
- pass
- return result
-
- def is_compatible(self):
- """
- Determine if a wheel is compatible with the running system.
- """
- return is_compatible(self)
-
- def is_mountable(self):
- """
- Determine if a wheel is asserted as mountable by its metadata.
- """
- return True # for now - metadata details TBD
-
- def mount(self, append=False):
- pathname = os.path.abspath(os.path.join(self.dirname, self.filename))
- if not self.is_compatible():
- msg = 'Wheel %s not compatible with this Python.' % pathname
- raise DistlibException(msg)
- if not self.is_mountable():
- msg = 'Wheel %s is marked as not mountable.' % pathname
- raise DistlibException(msg)
- if pathname in sys.path:
- logger.debug('%s already in path', pathname)
- else:
- if append:
- sys.path.append(pathname)
- else:
- sys.path.insert(0, pathname)
- extensions = self._get_extensions()
- if extensions:
- if _hook not in sys.meta_path:
- sys.meta_path.append(_hook)
- _hook.add(pathname, extensions)
-
- def unmount(self):
- pathname = os.path.abspath(os.path.join(self.dirname, self.filename))
- if pathname not in sys.path:
- logger.debug('%s not in path', pathname)
- else:
- sys.path.remove(pathname)
- if pathname in _hook.impure_wheels:
- _hook.remove(pathname)
- if not _hook.impure_wheels:
- if _hook in sys.meta_path:
- sys.meta_path.remove(_hook)
-
- def verify(self):
- pathname = os.path.join(self.dirname, self.filename)
- name_ver = '%s-%s' % (self.name, self.version)
- # data_dir = '%s.data' % name_ver
- info_dir = '%s.dist-info' % name_ver
-
- # metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME)
- wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
- record_name = posixpath.join(info_dir, 'RECORD')
-
- wrapper = codecs.getreader('utf-8')
-
- with ZipFile(pathname, 'r') as zf:
- with zf.open(wheel_metadata_name) as bwf:
- wf = wrapper(bwf)
- message_from_file(wf)
- # wv = message['Wheel-Version'].split('.', 1)
- # file_version = tuple([int(i) for i in wv])
- # TODO version verification
-
- records = {}
- with zf.open(record_name) as bf:
- with CSVReader(stream=bf) as reader:
- for row in reader:
- p = row[0]
- records[p] = row
-
- for zinfo in zf.infolist():
- arcname = zinfo.filename
- if isinstance(arcname, text_type):
- u_arcname = arcname
- else:
- u_arcname = arcname.decode('utf-8')
- # See issue #115: some wheels have .. in their entries, but
- # in the filename ... e.g. __main__..py ! So the check is
- # updated to look for .. in the directory portions
- p = u_arcname.split('/')
- if '..' in p:
- raise DistlibException('invalid entry in '
- 'wheel: %r' % u_arcname)
-
- if self.skip_entry(u_arcname):
- continue
- row = records[u_arcname]
- if row[2] and str(zinfo.file_size) != row[2]:
- raise DistlibException('size mismatch for '
- '%s' % u_arcname)
- if row[1]:
- kind, value = row[1].split('=', 1)
- with zf.open(arcname) as bf:
- data = bf.read()
- _, digest = self.get_hash(data, kind)
- if digest != value:
- raise DistlibException('digest mismatch for '
- '%s' % arcname)
-
- def update(self, modifier, dest_dir=None, **kwargs):
- """
- Update the contents of a wheel in a generic way. The modifier should
- be a callable which expects a dictionary argument: its keys are
- archive-entry paths, and its values are absolute filesystem paths
- where the contents the corresponding archive entries can be found. The
- modifier is free to change the contents of the files pointed to, add
- new entries and remove entries, before returning. This method will
- extract the entire contents of the wheel to a temporary location, call
- the modifier, and then use the passed (and possibly updated)
- dictionary to write a new wheel. If ``dest_dir`` is specified, the new
- wheel is written there -- otherwise, the original wheel is overwritten.
-
- The modifier should return True if it updated the wheel, else False.
- This method returns the same value the modifier returns.
- """
-
- def get_version(path_map, info_dir):
- version = path = None
- key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME)
- if key not in path_map:
- key = '%s/PKG-INFO' % info_dir
- if key in path_map:
- path = path_map[key]
- version = Metadata(path=path).version
- return version, path
-
- def update_version(version, path):
- updated = None
- try:
- NormalizedVersion(version)
- i = version.find('-')
- if i < 0:
- updated = '%s+1' % version
- else:
- parts = [int(s) for s in version[i + 1:].split('.')]
- parts[-1] += 1
- updated = '%s+%s' % (version[:i], '.'.join(str(i) for i in parts))
- except UnsupportedVersionError:
- logger.debug('Cannot update non-compliant (PEP-440) '
- 'version %r', version)
- if updated:
- md = Metadata(path=path)
- md.version = updated
- legacy = path.endswith(LEGACY_METADATA_FILENAME)
- md.write(path=path, legacy=legacy)
- logger.debug('Version updated from %r to %r', version, updated)
-
- pathname = os.path.join(self.dirname, self.filename)
- name_ver = '%s-%s' % (self.name, self.version)
- info_dir = '%s.dist-info' % name_ver
- record_name = posixpath.join(info_dir, 'RECORD')
- with tempdir() as workdir:
- with ZipFile(pathname, 'r') as zf:
- path_map = {}
- for zinfo in zf.infolist():
- arcname = zinfo.filename
- if isinstance(arcname, text_type):
- u_arcname = arcname
- else:
- u_arcname = arcname.decode('utf-8')
- if u_arcname == record_name:
- continue
- if '..' in u_arcname:
- raise DistlibException('invalid entry in '
- 'wheel: %r' % u_arcname)
- zf.extract(zinfo, workdir)
- path = os.path.join(workdir, convert_path(u_arcname))
- path_map[u_arcname] = path
-
- # Remember the version.
- original_version, _ = get_version(path_map, info_dir)
- # Files extracted. Call the modifier.
- modified = modifier(path_map, **kwargs)
- if modified:
- # Something changed - need to build a new wheel.
- current_version, path = get_version(path_map, info_dir)
- if current_version and (current_version == original_version):
- # Add or update local version to signify changes.
- update_version(current_version, path)
- # Decide where the new wheel goes.
- if dest_dir is None:
- fd, newpath = tempfile.mkstemp(suffix='.whl', prefix='wheel-update-', dir=workdir)
- os.close(fd)
- else:
- if not os.path.isdir(dest_dir):
- raise DistlibException('Not a directory: %r' % dest_dir)
- newpath = os.path.join(dest_dir, self.filename)
- archive_paths = list(path_map.items())
- distinfo = os.path.join(workdir, info_dir)
- info = distinfo, info_dir
- self.write_records(info, workdir, archive_paths)
- self.build_zip(newpath, archive_paths)
- if dest_dir is None:
- shutil.copyfile(newpath, pathname)
- return modified
-
-
-def _get_glibc_version():
- import platform
- ver = platform.libc_ver()
- result = []
- if ver[0] == 'glibc':
- for s in ver[1].split('.'):
- result.append(int(s) if s.isdigit() else 0)
- result = tuple(result)
- return result
-
-
-def compatible_tags():
- """
- Return (pyver, abi, arch) tuples compatible with this Python.
- """
- class _Version:
- def __init__(self, major, minor):
- self.major = major
- self.major_minor = (major, minor)
- self.string = ''.join((str(major), str(minor)))
-
- def __str__(self):
- return self.string
-
-
- versions = [
- _Version(sys.version_info.major, minor_version)
- for minor_version in range(sys.version_info.minor, -1, -1)
- ]
- abis = []
- for suffix in _get_suffixes():
- if suffix.startswith('.abi'):
- abis.append(suffix.split('.', 2)[1])
- abis.sort()
- if ABI != 'none':
- abis.insert(0, ABI)
- abis.append('none')
- result = []
-
- arches = [ARCH]
- if sys.platform == 'darwin':
- m = re.match(r'(\w+)_(\d+)_(\d+)_(\w+)$', ARCH)
- if m:
- name, major, minor, arch = m.groups()
- minor = int(minor)
- matches = [arch]
- if arch in ('i386', 'ppc'):
- matches.append('fat')
- if arch in ('i386', 'ppc', 'x86_64'):
- matches.append('fat3')
- if arch in ('ppc64', 'x86_64'):
- matches.append('fat64')
- if arch in ('i386', 'x86_64'):
- matches.append('intel')
- if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'):
- matches.append('universal')
- while minor >= 0:
- for match in matches:
- s = '%s_%s_%s_%s' % (name, major, minor, match)
- if s != ARCH: # already there
- arches.append(s)
- minor -= 1
-
- # Most specific - our Python version, ABI and arch
- for i, version_object in enumerate(versions):
- version = str(version_object)
- add_abis = []
-
- if i == 0:
- add_abis = abis
-
- if IMP_PREFIX == 'cp' and version_object.major_minor >= (3, 2):
- limited_api_abi = 'abi' + str(version_object.major)
- if limited_api_abi not in add_abis:
- add_abis.append(limited_api_abi)
-
- for abi in add_abis:
- for arch in arches:
- result.append((''.join((IMP_PREFIX, version)), abi, arch))
- # manylinux
- if abi != 'none' and sys.platform.startswith('linux'):
- arch = arch.replace('linux_', '')
- parts = _get_glibc_version()
- if len(parts) == 2:
- if parts >= (2, 5):
- result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux1_%s' % arch))
- if parts >= (2, 12):
- result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2010_%s' % arch))
- if parts >= (2, 17):
- result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2014_%s' % arch))
- result.append((''.join(
- (IMP_PREFIX, version)), abi, 'manylinux_%s_%s_%s' % (parts[0], parts[1], arch)))
-
- # where no ABI / arch dependency, but IMP_PREFIX dependency
- for i, version_object in enumerate(versions):
- version = str(version_object)
- result.append((''.join((IMP_PREFIX, version)), 'none', 'any'))
- if i == 0:
- result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any'))
-
- # no IMP_PREFIX, ABI or arch dependency
- for i, version_object in enumerate(versions):
- version = str(version_object)
- result.append((''.join(('py', version)), 'none', 'any'))
- if i == 0:
- result.append((''.join(('py', version[0])), 'none', 'any'))
-
- return set(result)
-
-
-COMPATIBLE_TAGS = compatible_tags()
-
-del compatible_tags
-
-
-def is_compatible(wheel, tags=None):
- if not isinstance(wheel, Wheel):
- wheel = Wheel(wheel) # assume it's a filename
- result = False
- if tags is None:
- tags = COMPATIBLE_TAGS
- for ver, abi, arch in tags:
- if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch:
- result = True
- break
- return result
diff --git a/contrib/python/pip/pip/_vendor/msgpack/__init__.py b/contrib/python/pip/pip/_vendor/msgpack/__init__.py
index b61510544a9..ad68271d864 100644
--- a/contrib/python/pip/pip/_vendor/msgpack/__init__.py
+++ b/contrib/python/pip/pip/_vendor/msgpack/__init__.py
@@ -4,8 +4,8 @@ import os
from .exceptions import * # noqa: F403
from .ext import ExtType, Timestamp
-version = (1, 1, 0)
-__version__ = "1.1.0"
+version = (1, 1, 1)
+__version__ = "1.1.1"
if os.environ.get("MSGPACK_PUREPYTHON"):
diff --git a/contrib/python/pip/pip/_vendor/pkg_resources/__init__.py b/contrib/python/pip/pip/_vendor/pkg_resources/__init__.py
index 57ce7f10064..72f2b03514d 100644
--- a/contrib/python/pip/pip/_vendor/pkg_resources/__init__.py
+++ b/contrib/python/pip/pip/_vendor/pkg_resources/__init__.py
@@ -100,7 +100,7 @@ from pip._vendor.platformdirs import user_cache_dir as _user_cache_dir
if TYPE_CHECKING:
from _typeshed import BytesPath, StrPath, StrOrBytesPath
- from pip._vendor.typing_extensions import Self
+ from typing_extensions import Self
# Patch: Remove deprecation warning from vendored pkg_resources.
diff --git a/contrib/python/pip/pip/_vendor/platformdirs/version.py b/contrib/python/pip/pip/_vendor/platformdirs/version.py
index ed85187adca..611ac615443 100644
--- a/contrib/python/pip/pip/_vendor/platformdirs/version.py
+++ b/contrib/python/pip/pip/_vendor/platformdirs/version.py
@@ -17,5 +17,5 @@ __version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
-__version__ = version = '4.3.7'
-__version_tuple__ = version_tuple = (4, 3, 7)
+__version__ = version = '4.3.8'
+__version_tuple__ = version_tuple = (4, 3, 8)
diff --git a/contrib/python/pip/pip/_vendor/pygments/__init__.py b/contrib/python/pip/pip/_vendor/pygments/__init__.py
index 38e059a35d9..cb229c98500 100644
--- a/contrib/python/pip/pip/_vendor/pygments/__init__.py
+++ b/contrib/python/pip/pip/_vendor/pygments/__init__.py
@@ -26,7 +26,7 @@
"""
from io import StringIO, BytesIO
-__version__ = '2.19.1'
+__version__ = '2.19.2'
__docformat__ = 'restructuredtext'
__all__ = ['lex', 'format', 'highlight']
diff --git a/contrib/python/pip/pip/_vendor/requests/__version__.py b/contrib/python/pip/pip/_vendor/requests/__version__.py
index 2c105aca7d4..3128a4644ec 100644
--- a/contrib/python/pip/pip/_vendor/requests/__version__.py
+++ b/contrib/python/pip/pip/_vendor/requests/__version__.py
@@ -5,8 +5,8 @@
__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
-__version__ = "2.32.3"
-__build__ = 0x023203
+__version__ = "2.32.4"
+__build__ = 0x023204
__author__ = "Kenneth Reitz"
__author_email__ = "[email protected]"
__license__ = "Apache-2.0"
diff --git a/contrib/python/pip/pip/_vendor/requests/compat.py b/contrib/python/pip/pip/_vendor/requests/compat.py
index 7081da756ac..b95a92140e2 100644
--- a/contrib/python/pip/pip/_vendor/requests/compat.py
+++ b/contrib/python/pip/pip/_vendor/requests/compat.py
@@ -9,6 +9,18 @@ compatibility until the next major version.
import sys
+# -------
+# urllib3
+# -------
+from pip._vendor.urllib3 import __version__ as urllib3_version
+
+# Detect which major version of urllib3 is being used.
+try:
+ is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1
+except (TypeError, AttributeError):
+ # If we can't discern a version, prefer old functionality.
+ is_urllib3_1 = True
+
# -------------------
# Character Detection
# -------------------
diff --git a/contrib/python/pip/pip/_vendor/requests/models.py b/contrib/python/pip/pip/_vendor/requests/models.py
index 85a008cfb56..22de95c0612 100644
--- a/contrib/python/pip/pip/_vendor/requests/models.py
+++ b/contrib/python/pip/pip/_vendor/requests/models.py
@@ -945,7 +945,9 @@ class Response:
return content
def json(self, **kwargs):
- r"""Returns the json-encoded content of a response, if any.
+ r"""Decodes the JSON response body (if any) as a Python object.
+
+ This may return a dictionary, list, etc. depending on what is in the response.
:param \*\*kwargs: Optional arguments that ``json.loads`` takes.
:raises requests.exceptions.JSONDecodeError: If the response body does not
diff --git a/contrib/python/pip/pip/_vendor/requests/utils.py b/contrib/python/pip/pip/_vendor/requests/utils.py
index a35ce478667..e8ea5ad3674 100644
--- a/contrib/python/pip/pip/_vendor/requests/utils.py
+++ b/contrib/python/pip/pip/_vendor/requests/utils.py
@@ -38,6 +38,7 @@ from .compat import (
getproxies,
getproxies_environment,
integer_types,
+ is_urllib3_1,
)
from .compat import parse_http_list as _parse_list_header
from .compat import (
@@ -136,7 +137,9 @@ def super_len(o):
total_length = None
current_position = 0
- if isinstance(o, str):
+ if not is_urllib3_1 and isinstance(o, str):
+ # urllib3 2.x+ treats all strings as utf-8 instead
+ # of latin-1 (iso-8859-1) like http.client.
o = o.encode("utf-8")
if hasattr(o, "__len__"):
@@ -216,14 +219,7 @@ def get_netrc_auth(url, raise_errors=False):
netrc_path = None
for f in netrc_locations:
- try:
- loc = os.path.expanduser(f)
- except KeyError:
- # os.path.expanduser can fail when $HOME is undefined and
- # getpwuid fails. See https://bugs.python.org/issue20164 &
- # https://github.com/psf/requests/issues/1846
- return
-
+ loc = os.path.expanduser(f)
if os.path.exists(loc):
netrc_path = loc
break
@@ -233,13 +229,7 @@ def get_netrc_auth(url, raise_errors=False):
return
ri = urlparse(url)
-
- # Strip port numbers from netloc. This weird `if...encode`` dance is
- # used for Python 3.2, which doesn't support unicode literals.
- splitstr = b":"
- if isinstance(url, str):
- splitstr = splitstr.decode("ascii")
- host = ri.netloc.split(splitstr)[0]
+ host = ri.hostname
try:
_netrc = netrc(netrc_path).authenticators(host)
diff --git a/contrib/python/pip/pip/_vendor/resolvelib/__init__.py b/contrib/python/pip/pip/_vendor/resolvelib/__init__.py
index c655c597c6f..41537e3e020 100644
--- a/contrib/python/pip/pip/_vendor/resolvelib/__init__.py
+++ b/contrib/python/pip/pip/_vendor/resolvelib/__init__.py
@@ -1,17 +1,17 @@
__all__ = [
- "__version__",
"AbstractProvider",
"AbstractResolver",
"BaseReporter",
"InconsistentCandidate",
- "Resolver",
"RequirementsConflicted",
"ResolutionError",
"ResolutionImpossible",
"ResolutionTooDeep",
+ "Resolver",
+ "__version__",
]
-__version__ = "1.1.0"
+__version__ = "1.2.0"
from .providers import AbstractProvider
diff --git a/contrib/python/pip/pip/_vendor/resolvelib/reporters.py b/contrib/python/pip/pip/_vendor/resolvelib/reporters.py
index 26c9f6e6f92..6c142204b7d 100644
--- a/contrib/python/pip/pip/_vendor/resolvelib/reporters.py
+++ b/contrib/python/pip/pip/_vendor/resolvelib/reporters.py
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
class BaseReporter(Generic[RT, CT, KT]):
- """Delegate class to provider progress reporting for the resolver."""
+ """Delegate class to provide progress reporting for the resolver."""
def starting(self) -> None:
"""Called before the resolution actually starts."""
diff --git a/contrib/python/pip/pip/_vendor/resolvelib/resolvers/__init__.py b/contrib/python/pip/pip/_vendor/resolvelib/resolvers/__init__.py
index 7b2c5d597eb..b24922152a3 100644
--- a/contrib/python/pip/pip/_vendor/resolvelib/resolvers/__init__.py
+++ b/contrib/python/pip/pip/_vendor/resolvelib/resolvers/__init__.py
@@ -13,15 +13,15 @@ from .resolution import Resolution, Resolver
__all__ = [
"AbstractResolver",
+ "Criterion",
"InconsistentCandidate",
- "Resolver",
- "Resolution",
+ "RequirementInformation",
"RequirementsConflicted",
+ "Resolution",
"ResolutionError",
"ResolutionImpossible",
"ResolutionTooDeep",
- "RequirementInformation",
+ "Resolver",
"ResolverException",
"Result",
- "Criterion",
]
diff --git a/contrib/python/pip/pip/_vendor/resolvelib/resolvers/resolution.py b/contrib/python/pip/pip/_vendor/resolvelib/resolvers/resolution.py
index da3c66e2ab7..f55ac7ab928 100644
--- a/contrib/python/pip/pip/_vendor/resolvelib/resolvers/resolution.py
+++ b/contrib/python/pip/pip/_vendor/resolvelib/resolvers/resolution.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import collections
import itertools
import operator
-from typing import TYPE_CHECKING, Collection, Generic, Iterable, Mapping
+from typing import TYPE_CHECKING, Generic
from ..structs import (
CT,
@@ -27,9 +27,13 @@ from .exceptions import (
)
if TYPE_CHECKING:
+ from collections.abc import Collection, Iterable, Mapping
+
from ..providers import AbstractProvider, Preference
from ..reporters import BaseReporter
+_OPTIMISTIC_BACKJUMPING_RATIO: float = 0.1
+
def _build_result(state: State[RT, CT, KT]) -> Result[RT, CT, KT]:
mapping = state.mapping
@@ -77,6 +81,11 @@ class Resolution(Generic[RT, CT, KT]):
self._r = reporter
self._states: list[State[RT, CT, KT]] = []
+ # Optimistic backjumping variables
+ self._optimistic_backjumping_ratio = _OPTIMISTIC_BACKJUMPING_RATIO
+ self._save_states: list[State[RT, CT, KT]] | None = None
+ self._optimistic_start_round: int | None = None
+
@property
def state(self) -> State[RT, CT, KT]:
try:
@@ -274,6 +283,25 @@ class Resolution(Generic[RT, CT, KT]):
)
return True
+ def _save_state(self) -> None:
+ """Save states for potential rollback if optimistic backjumping fails."""
+ if self._save_states is None:
+ self._save_states = [
+ State(
+ mapping=s.mapping.copy(),
+ criteria=s.criteria.copy(),
+ backtrack_causes=s.backtrack_causes[:],
+ )
+ for s in self._states
+ ]
+
+ def _rollback_states(self) -> None:
+ """Rollback states and disable optimistic backjumping."""
+ self._optimistic_backjumping_ratio = 0.0
+ if self._save_states:
+ self._states = self._save_states
+ self._save_states = None
+
def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool:
"""Perform backjumping.
@@ -324,13 +352,26 @@ class Resolution(Generic[RT, CT, KT]):
except (IndexError, KeyError):
raise ResolutionImpossible(causes) from None
- # Only backjump if the current broken state is
- # an incompatible dependency
- if name not in incompatible_deps:
+ if (
+ not self._optimistic_backjumping_ratio
+ and name not in incompatible_deps
+ ):
+ # For safe backjumping only backjump if the current dependency
+ # is not the same as the incompatible dependency
break
+ # On the first time a non-safe backjump is done the state
+ # is saved so we can restore it later if the resolution fails
+ if (
+ self._optimistic_backjumping_ratio
+ and self._save_states is None
+ and name not in incompatible_deps
+ ):
+ self._save_state()
+
# If the current dependencies and the incompatible dependencies
- # are overlapping then we have found a cause of the incompatibility
+ # are overlapping then we have likely found a cause of the
+ # incompatibility
current_dependencies = {
self._p.identify(d) for d in self._p.get_dependencies(candidate)
}
@@ -394,9 +435,32 @@ class Resolution(Generic[RT, CT, KT]):
# pinning the virtual "root" package in the graph.
self._push_new_state()
+ # Variables for optimistic backjumping
+ optimistic_rounds_cutoff: int | None = None
+ optimistic_backjumping_start_round: int | None = None
+
for round_index in range(max_rounds):
self._r.starting_round(index=round_index)
+ # Handle if optimistic backjumping has been running for too long
+ if self._optimistic_backjumping_ratio and self._save_states is not None:
+ if optimistic_backjumping_start_round is None:
+ optimistic_backjumping_start_round = round_index
+ optimistic_rounds_cutoff = int(
+ (max_rounds - round_index) * self._optimistic_backjumping_ratio
+ )
+
+ if optimistic_rounds_cutoff <= 0:
+ self._rollback_states()
+ continue
+ elif optimistic_rounds_cutoff is not None:
+ if (
+ round_index - optimistic_backjumping_start_round
+ >= optimistic_rounds_cutoff
+ ):
+ self._rollback_states()
+ continue
+
unsatisfied_names = [
key
for key, criterion in self.state.criteria.items()
@@ -448,12 +512,29 @@ class Resolution(Generic[RT, CT, KT]):
# Backjump if pinning fails. The backjump process puts us in
# an unpinned state, so we can work on it in the next round.
self._r.resolving_conflicts(causes=causes)
- success = self._backjump(causes)
- self.state.backtrack_causes[:] = causes
- # Dead ends everywhere. Give up.
- if not success:
- raise ResolutionImpossible(self.state.backtrack_causes)
+ try:
+ success = self._backjump(causes)
+ except ResolutionImpossible:
+ if self._optimistic_backjumping_ratio and self._save_states:
+ failed_optimistic_backjumping = True
+ else:
+ raise
+ else:
+ failed_optimistic_backjumping = bool(
+ not success
+ and self._optimistic_backjumping_ratio
+ and self._save_states
+ )
+
+ if failed_optimistic_backjumping and self._save_states:
+ self._rollback_states()
+ else:
+ self.state.backtrack_causes[:] = causes
+
+ # Dead ends everywhere. Give up.
+ if not success:
+ raise ResolutionImpossible(self.state.backtrack_causes)
else:
# discard as information sources any invalidated names
# (unsatisfied names that were previously satisfied)
diff --git a/contrib/python/pip/pip/_vendor/rich/__main__.py b/contrib/python/pip/pip/_vendor/rich/__main__.py
index efb7fb79bf6..a583f1efae3 100644
--- a/contrib/python/pip/pip/_vendor/rich/__main__.py
+++ b/contrib/python/pip/pip/_vendor/rich/__main__.py
@@ -207,6 +207,8 @@ Supports much of the *markdown* __syntax__!
if __name__ == "__main__": # pragma: no cover
+ from pip._vendor.rich.panel import Panel
+
console = Console(
file=io.StringIO(),
force_terminal=True,
@@ -227,47 +229,17 @@ if __name__ == "__main__": # pragma: no cover
c = Console(record=True)
c.print(test_card)
- print(f"rendered in {pre_cache_taken}ms (cold cache)")
- print(f"rendered in {taken}ms (warm cache)")
-
- from pip._vendor.rich.panel import Panel
-
console = Console()
-
- sponsor_message = Table.grid(padding=1)
- sponsor_message.add_column(style="green", justify="right")
- sponsor_message.add_column(no_wrap=True)
-
- sponsor_message.add_row(
- "Textualize",
- "[u blue link=https://github.com/textualize]https://github.com/textualize",
- )
- sponsor_message.add_row(
- "Twitter",
- "[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan",
- )
-
- intro_message = Text.from_markup(
- """\
-We hope you enjoy using Rich!
-
-Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/]
-
-- Will McGugan"""
- )
-
- message = Table.grid(padding=2)
- message.add_column()
- message.add_column(no_wrap=True)
- message.add_row(intro_message, sponsor_message)
-
+ console.print(f"[dim]rendered in [not dim]{pre_cache_taken}ms[/] (cold cache)")
+ console.print(f"[dim]rendered in [not dim]{taken}ms[/] (warm cache)")
+ console.print()
console.print(
Panel.fit(
- message,
- box=box.ROUNDED,
- padding=(1, 2),
- title="[b red]Thanks for trying out Rich!",
- border_style="bright_blue",
- ),
- justify="center",
+ "[b magenta]Hope you enjoy using Rich![/]\n\n"
+ "Please consider sponsoring me if you get value from my work.\n\n"
+ "Even the price of a ☕ can brighten my day!\n\n"
+ "https://github.com/sponsors/willmcgugan",
+ border_style="red",
+ title="Help ensure Rich is maintained",
+ )
)
diff --git a/contrib/python/pip/pip/_vendor/rich/_inspect.py b/contrib/python/pip/pip/_vendor/rich/_inspect.py
index e87698d1fa8..27d65cec93b 100644
--- a/contrib/python/pip/pip/_vendor/rich/_inspect.py
+++ b/contrib/python/pip/pip/_vendor/rich/_inspect.py
@@ -214,7 +214,7 @@ class Inspect(JupyterMixin):
def _get_formatted_doc(self, object_: Any) -> Optional[str]:
"""
Extract the docstring of an object, process it and returns it.
- The processing consists in cleaning up the doctring's indentation,
+ The processing consists in cleaning up the docstring's indentation,
taking only its 1st paragraph if `self.help` is not True,
and escape its control codes.
diff --git a/contrib/python/pip/pip/_vendor/rich/_ratio.py b/contrib/python/pip/pip/_vendor/rich/_ratio.py
index 95267b0cb6c..5fd5a383d22 100644
--- a/contrib/python/pip/pip/_vendor/rich/_ratio.py
+++ b/contrib/python/pip/pip/_vendor/rich/_ratio.py
@@ -1,12 +1,6 @@
-import sys
from fractions import Fraction
from math import ceil
-from typing import cast, List, Optional, Sequence
-
-if sys.version_info >= (3, 8):
- from typing import Protocol
-else:
- from pip._vendor.typing_extensions import Protocol # pragma: no cover
+from typing import cast, List, Optional, Sequence, Protocol
class Edge(Protocol):
diff --git a/contrib/python/pip/pip/_vendor/rich/align.py b/contrib/python/pip/pip/_vendor/rich/align.py
index 330dcc51192..e65dc5ba256 100644
--- a/contrib/python/pip/pip/_vendor/rich/align.py
+++ b/contrib/python/pip/pip/_vendor/rich/align.py
@@ -1,11 +1,5 @@
-import sys
from itertools import chain
-from typing import TYPE_CHECKING, Iterable, Optional
-
-if sys.version_info >= (3, 8):
- from typing import Literal
-else:
- from pip._vendor.typing_extensions import Literal # pragma: no cover
+from typing import TYPE_CHECKING, Iterable, Optional, Literal
from .constrain import Constrain
from .jupyter import JupyterMixin
diff --git a/contrib/python/pip/pip/_vendor/rich/box.py b/contrib/python/pip/pip/_vendor/rich/box.py
index 0511a9e48ba..3f330ccb57b 100644
--- a/contrib/python/pip/pip/_vendor/rich/box.py
+++ b/contrib/python/pip/pip/_vendor/rich/box.py
@@ -1,10 +1,4 @@
-import sys
-from typing import TYPE_CHECKING, Iterable, List
-
-if sys.version_info >= (3, 8):
- from typing import Literal
-else:
- from pip._vendor.typing_extensions import Literal # pragma: no cover
+from typing import TYPE_CHECKING, Iterable, List, Literal
from ._loop import loop_last
diff --git a/contrib/python/pip/pip/_vendor/rich/console.py b/contrib/python/pip/pip/_vendor/rich/console.py
index 57474835f92..db2ba55abd1 100644
--- a/contrib/python/pip/pip/_vendor/rich/console.py
+++ b/contrib/python/pip/pip/_vendor/rich/console.py
@@ -22,27 +22,21 @@ from typing import (
Dict,
Iterable,
List,
+ Literal,
Mapping,
NamedTuple,
Optional,
+ Protocol,
TextIO,
Tuple,
Type,
Union,
cast,
+ runtime_checkable,
)
from pip._vendor.rich._null_file import NULL_FILE
-if sys.version_info >= (3, 8):
- from typing import Literal, Protocol, runtime_checkable
-else:
- from pip._vendor.typing_extensions import (
- Literal,
- Protocol,
- runtime_checkable,
- ) # pragma: no cover
-
from . import errors, themes
from ._emoji_replace import _emoji_replace
from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
@@ -739,6 +733,14 @@ class Console:
if no_color is not None
else self._environ.get("NO_COLOR", "") != ""
)
+ if force_interactive is None:
+ tty_interactive = self._environ.get("TTY_INTERACTIVE", None)
+ if tty_interactive is not None:
+ if tty_interactive == "0":
+ force_interactive = False
+ elif tty_interactive == "1":
+ force_interactive = True
+
self.is_interactive = (
(self.is_terminal and not self.is_dumb_terminal)
if force_interactive is None
@@ -751,7 +753,7 @@ class Console:
)
self._record_buffer: List[Segment] = []
self._render_hooks: List[RenderHook] = []
- self._live: Optional["Live"] = None
+ self._live_stack: List[Live] = []
self._is_alt_screen = False
def __repr__(self) -> str:
@@ -823,24 +825,26 @@ class Console:
self._buffer_index -= 1
self._check_buffer()
- def set_live(self, live: "Live") -> None:
- """Set Live instance. Used by Live context manager.
+ def set_live(self, live: "Live") -> bool:
+ """Set Live instance. Used by Live context manager (no need to call directly).
Args:
live (Live): Live instance using this Console.
+ Returns:
+ Boolean that indicates if the live is the topmost of the stack.
+
Raises:
errors.LiveError: If this Console has a Live context currently active.
"""
with self._lock:
- if self._live is not None:
- raise errors.LiveError("Only one live display may be active at once")
- self._live = live
+ self._live_stack.append(live)
+ return len(self._live_stack) == 1
def clear_live(self) -> None:
- """Clear the Live instance."""
+ """Clear the Live instance. Used by the Live context manager (no need to call directly)."""
with self._lock:
- self._live = None
+ self._live_stack.pop()
def push_render_hook(self, hook: RenderHook) -> None:
"""Add a new render hook to the stack.
@@ -992,12 +996,13 @@ class Console:
@property
def options(self) -> ConsoleOptions:
"""Get default console options."""
+ size = self.size
return ConsoleOptions(
- max_height=self.size.height,
- size=self.size,
+ max_height=size.height,
+ size=size,
legacy_windows=self.legacy_windows,
min_width=1,
- max_width=self.width,
+ max_width=size.width,
encoding=self.encoding,
is_terminal=self.is_terminal,
)
diff --git a/contrib/python/pip/pip/_vendor/rich/control.py b/contrib/python/pip/pip/_vendor/rich/control.py
index 88fcb929516..84963e9dd61 100644
--- a/contrib/python/pip/pip/_vendor/rich/control.py
+++ b/contrib/python/pip/pip/_vendor/rich/control.py
@@ -1,11 +1,5 @@
-import sys
import time
-from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union
-
-if sys.version_info >= (3, 8):
- from typing import Final
-else:
- from pip._vendor.typing_extensions import Final # pragma: no cover
+from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union, Final
from .segment import ControlCode, ControlType, Segment
diff --git a/contrib/python/pip/pip/_vendor/rich/diagnose.py b/contrib/python/pip/pip/_vendor/rich/diagnose.py
index b8b8c4347e8..92893b32c54 100644
--- a/contrib/python/pip/pip/_vendor/rich/diagnose.py
+++ b/contrib/python/pip/pip/_vendor/rich/diagnose.py
@@ -26,6 +26,7 @@ def report() -> None: # pragma: no cover
"TERM_PROGRAM",
"TERM",
"TTY_COMPATIBLE",
+ "TTY_INTERACTIVE",
"VSCODE_VERBOSE_LOGGING",
)
env = {name: os.getenv(name) for name in env_names}
diff --git a/contrib/python/pip/pip/_vendor/rich/emoji.py b/contrib/python/pip/pip/_vendor/rich/emoji.py
index 791f0465de1..4a667edde73 100644
--- a/contrib/python/pip/pip/_vendor/rich/emoji.py
+++ b/contrib/python/pip/pip/_vendor/rich/emoji.py
@@ -1,5 +1,5 @@
import sys
-from typing import TYPE_CHECKING, Optional, Union
+from typing import TYPE_CHECKING, Optional, Union, Literal
from .jupyter import JupyterMixin
from .segment import Segment
@@ -7,11 +7,6 @@ from .style import Style
from ._emoji_codes import EMOJI
from ._emoji_replace import _emoji_replace
-if sys.version_info >= (3, 8):
- from typing import Literal
-else:
- from pip._vendor.typing_extensions import Literal # pragma: no cover
-
if TYPE_CHECKING:
from .console import Console, ConsoleOptions, RenderResult
diff --git a/contrib/python/pip/pip/_vendor/rich/live.py b/contrib/python/pip/pip/_vendor/rich/live.py
index 8738cf09f49..cc3a39bd577 100644
--- a/contrib/python/pip/pip/_vendor/rich/live.py
+++ b/contrib/python/pip/pip/_vendor/rich/live.py
@@ -1,10 +1,12 @@
+from __future__ import annotations
+
import sys
from threading import Event, RLock, Thread
from types import TracebackType
-from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast
+from typing import IO, TYPE_CHECKING, Any, Callable, List, Optional, TextIO, Type, cast
from . import get_console
-from .console import Console, ConsoleRenderable, RenderableType, RenderHook
+from .console import Console, ConsoleRenderable, Group, RenderableType, RenderHook
from .control import Control
from .file_proxy import FileProxy
from .jupyter import JupyterMixin
@@ -12,6 +14,10 @@ from .live_render import LiveRender, VerticalOverflowMethod
from .screen import Screen
from .text import Text
+if TYPE_CHECKING:
+ # Can be replaced with `from typing import Self` in Python 3.11+
+ from typing_extensions import Self # pragma: no cover
+
class _RefreshThread(Thread):
"""A thread that calls refresh() at regular intervals."""
@@ -87,6 +93,7 @@ class Live(JupyterMixin, RenderHook):
self._live_render = LiveRender(
self.get_renderable(), vertical_overflow=vertical_overflow
)
+ self._nested = False
@property
def is_started(self) -> bool:
@@ -110,8 +117,12 @@ class Live(JupyterMixin, RenderHook):
with self._lock:
if self._started:
return
- self.console.set_live(self)
self._started = True
+
+ if not self.console.set_live(self):
+ self._nested = True
+ return
+
if self._screen:
self._alt_screen = self.console.set_alt_screen(True)
self.console.show_cursor(False)
@@ -136,8 +147,12 @@ class Live(JupyterMixin, RenderHook):
with self._lock:
if not self._started:
return
- self.console.clear_live()
self._started = False
+ self.console.clear_live()
+ if self._nested:
+ if not self.transient:
+ self.console.print(self.renderable)
+ return
if self.auto_refresh and self._refresh_thread is not None:
self._refresh_thread.stop()
@@ -156,13 +171,12 @@ class Live(JupyterMixin, RenderHook):
self.console.show_cursor(True)
if self._alt_screen:
self.console.set_alt_screen(False)
-
if self.transient and not self._alt_screen:
self.console.control(self._live_render.restore_cursor())
if self.ipy_widget is not None and self.transient:
self.ipy_widget.close() # pragma: no cover
- def __enter__(self) -> "Live":
+ def __enter__(self) -> Self:
self.start(refresh=self._renderable is not None)
return self
@@ -200,7 +214,13 @@ class Live(JupyterMixin, RenderHook):
Returns:
RenderableType: Displayed renderable.
"""
- renderable = self.get_renderable()
+ live_stack = self.console._live_stack
+ renderable: RenderableType
+ if live_stack and self is live_stack[0]:
+ # The first Live instance will render everything in the Live stack
+ renderable = Group(*[live.get_renderable() for live in live_stack])
+ else:
+ renderable = self.get_renderable()
return Screen(renderable) if self._alt_screen else renderable
def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
@@ -221,6 +241,11 @@ class Live(JupyterMixin, RenderHook):
"""Update the display of the Live Render."""
with self._lock:
self._live_render.set_renderable(self.renderable)
+ if self._nested:
+ if self.console._live_stack:
+ self.console._live_stack[0].refresh()
+ return
+
if self.console.is_jupyter: # pragma: no cover
try:
from IPython.display import display
diff --git a/contrib/python/pip/pip/_vendor/rich/live_render.py b/contrib/python/pip/pip/_vendor/rich/live_render.py
index e20745df6bf..d3da5111d82 100644
--- a/contrib/python/pip/pip/_vendor/rich/live_render.py
+++ b/contrib/python/pip/pip/_vendor/rich/live_render.py
@@ -1,10 +1,4 @@
-import sys
-from typing import Optional, Tuple
-
-if sys.version_info >= (3, 8):
- from typing import Literal
-else:
- from pip._vendor.typing_extensions import Literal # pragma: no cover
+from typing import Optional, Tuple, Literal
from ._loop import loop_last
diff --git a/contrib/python/pip/pip/_vendor/rich/logging.py b/contrib/python/pip/pip/_vendor/rich/logging.py
index ff8d5d95f49..08f34c70b37 100644
--- a/contrib/python/pip/pip/_vendor/rich/logging.py
+++ b/contrib/python/pip/pip/_vendor/rich/logging.py
@@ -76,7 +76,7 @@ class RichHandler(Handler):
markup: bool = False,
rich_tracebacks: bool = False,
tracebacks_width: Optional[int] = None,
- tracebacks_code_width: int = 88,
+ tracebacks_code_width: Optional[int] = 88,
tracebacks_extra_lines: int = 3,
tracebacks_theme: Optional[str] = None,
tracebacks_word_wrap: bool = True,
diff --git a/contrib/python/pip/pip/_vendor/rich/panel.py b/contrib/python/pip/pip/_vendor/rich/panel.py
index d411e291533..07587e3b338 100644
--- a/contrib/python/pip/pip/_vendor/rich/panel.py
+++ b/contrib/python/pip/pip/_vendor/rich/panel.py
@@ -146,8 +146,7 @@ class Panel(JupyterMixin):
Padding(self.renderable, _padding) if any(_padding) else self.renderable
)
style = console.get_style(self.style)
- partial_border_style = console.get_style(self.border_style)
- border_style = style + partial_border_style
+ border_style = style + console.get_style(self.border_style)
width = (
options.max_width
if self.width is None
@@ -206,7 +205,7 @@ class Panel(JupyterMixin):
title_text = self._title
if title_text is not None:
- title_text.stylize_before(partial_border_style)
+ title_text.stylize_before(border_style)
child_width = (
width - 2
@@ -255,7 +254,7 @@ class Panel(JupyterMixin):
subtitle_text = self._subtitle
if subtitle_text is not None:
- subtitle_text.stylize_before(partial_border_style)
+ subtitle_text.stylize_before(border_style)
if subtitle_text is None or width <= 4:
yield Segment(box.get_bottom([width - 2]), border_style)
diff --git a/contrib/python/pip/pip/_vendor/rich/progress.py b/contrib/python/pip/pip/_vendor/rich/progress.py
index ec086d9885d..ef6ad60f08d 100644
--- a/contrib/python/pip/pip/_vendor/rich/progress.py
+++ b/contrib/python/pip/pip/_vendor/rich/progress.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import io
-import sys
import typing
import warnings
from abc import ABC, abstractmethod
@@ -14,6 +15,7 @@ from os import PathLike, stat
from threading import Event, RLock, Thread
from types import TracebackType
from typing import (
+ TYPE_CHECKING,
Any,
BinaryIO,
Callable,
@@ -23,10 +25,10 @@ from typing import (
Generic,
Iterable,
List,
+ Literal,
NamedTuple,
NewType,
Optional,
- Sequence,
TextIO,
Tuple,
Type,
@@ -34,15 +36,9 @@ from typing import (
Union,
)
-if sys.version_info >= (3, 8):
- from typing import Literal
-else:
- from pip._vendor.typing_extensions import Literal # pragma: no cover
-
-if sys.version_info >= (3, 11):
- from typing import Self
-else:
- from pip._vendor.typing_extensions import Self # pragma: no cover
+if TYPE_CHECKING:
+ # Can be replaced with `from typing import Self` in Python 3.11+
+ from typing_extensions import Self # pragma: no cover
from . import filesize, get_console
from .console import Console, Group, JustifyMethod, RenderableType
@@ -106,7 +102,7 @@ class _TrackThread(Thread):
def track(
- sequence: Union[Sequence[ProgressType], Iterable[ProgressType]],
+ sequence: Iterable[ProgressType],
description: str = "Working...",
total: Optional[float] = None,
completed: int = 0,
@@ -125,8 +121,10 @@ def track(
) -> Iterable[ProgressType]:
"""Track progress by iterating over a sequence.
+ You can also track progress of an iterable, which might require that you additionally specify ``total``.
+
Args:
- sequence (Iterable[ProgressType]): A sequence (must support "len") you wish to iterate over.
+ sequence (Iterable[ProgressType]): Values you wish to iterate over and track progress.
description (str, optional): Description of task show next to progress bar. Defaults to "Working".
total: (float, optional): Total number of steps. Default is len(sequence).
completed (int, optional): Number of steps completed so far. Defaults to 0.
@@ -1192,7 +1190,7 @@ class Progress(JupyterMixin):
def track(
self,
- sequence: Union[Iterable[ProgressType], Sequence[ProgressType]],
+ sequence: Iterable[ProgressType],
total: Optional[float] = None,
completed: int = 0,
task_id: Optional[TaskID] = None,
@@ -1201,8 +1199,10 @@ class Progress(JupyterMixin):
) -> Iterable[ProgressType]:
"""Track progress by iterating over a sequence.
+ You can also track progress of an iterable, which might require that you additionally specify ``total``.
+
Args:
- sequence (Sequence[ProgressType]): A sequence of values you want to iterate over and track progress.
+ sequence (Iterable[ProgressType]): Values you want to iterate over and track progress.
total: (float, optional): Total number of steps. Default is len(sequence).
completed (int, optional): Number of steps completed so far. Defaults to 0.
task_id: (TaskID): Task to track. Default is new task.
diff --git a/contrib/python/pip/pip/_vendor/rich/spinner.py b/contrib/python/pip/pip/_vendor/rich/spinner.py
index 70570b6b096..a3a3caf84c6 100644
--- a/contrib/python/pip/pip/_vendor/rich/spinner.py
+++ b/contrib/python/pip/pip/_vendor/rich/spinner.py
@@ -1,4 +1,4 @@
-from typing import cast, List, Optional, TYPE_CHECKING, Union
+from typing import TYPE_CHECKING, List, Optional, Union, cast
from ._spinners import SPINNERS
from .measure import Measurement
@@ -6,7 +6,7 @@ from .table import Table
from .text import Text
if TYPE_CHECKING:
- from .console import Console, ConsoleOptions, RenderResult, RenderableType
+ from .console import Console, ConsoleOptions, RenderableType, RenderResult
from .style import StyleType
@@ -117,22 +117,16 @@ class Spinner:
if __name__ == "__main__": # pragma: no cover
from time import sleep
- from .columns import Columns
- from .panel import Panel
+ from .console import Group
from .live import Live
- all_spinners = Columns(
- [
+ all_spinners = Group(
+ *[
Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
for spinner_name in sorted(SPINNERS.keys())
- ],
- column_first=True,
- expand=True,
+ ]
)
- with Live(
- Panel(all_spinners, title="Spinners", border_style="blue"),
- refresh_per_second=20,
- ) as live:
+ with Live(all_spinners, refresh_per_second=20) as live:
while True:
sleep(0.1)
diff --git a/contrib/python/pip/pip/_vendor/rich/syntax.py b/contrib/python/pip/pip/_vendor/rich/syntax.py
index f3d483c3d07..ec0d903e028 100644
--- a/contrib/python/pip/pip/_vendor/rich/syntax.py
+++ b/contrib/python/pip/pip/_vendor/rich/syntax.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import re
import sys
@@ -224,6 +226,17 @@ class _SyntaxHighlightRange(NamedTuple):
style_before: bool = False
+class PaddingProperty:
+ """Descriptor to get and set padding."""
+
+ def __get__(self, obj: Syntax, objtype: Type[Syntax]) -> Tuple[int, int, int, int]:
+ """Space around the Syntax."""
+ return obj._padding
+
+ def __set__(self, obj: Syntax, padding: PaddingDimensions) -> None:
+ obj._padding = Padding.unpack(padding)
+
+
class Syntax(JupyterMixin):
"""Construct a Syntax object to render syntax highlighted code.
@@ -293,11 +306,13 @@ class Syntax(JupyterMixin):
Style(bgcolor=background_color) if background_color else Style()
)
self.indent_guides = indent_guides
- self.padding = padding
+ self._padding = Padding.unpack(padding)
self._theme = self.get_theme(theme)
self._stylized_ranges: List[_SyntaxHighlightRange] = []
+ padding = PaddingProperty()
+
@classmethod
def from_path(
cls,
@@ -371,8 +386,8 @@ class Syntax(JupyterMixin):
is supplied, the lexer will be chosen based on the file extension..
Args:
- path (AnyStr): The path to the file containing the code you wish to know the lexer for.
- code (str, optional): Optional string of code that will be used as a fallback if no lexer
+ path (AnyStr): The path to the file containing the code you wish to know the lexer for.
+ code (str, optional): Optional string of code that will be used as a fallback if no lexer
is found for the supplied path.
Returns:
@@ -607,7 +622,7 @@ class Syntax(JupyterMixin):
def __rich_measure__(
self, console: "Console", options: "ConsoleOptions"
) -> "Measurement":
- _, right, _, left = Padding.unpack(self.padding)
+ _, right, _, left = self.padding
padding = left + right
if self.code_width is not None:
width = self.code_width + self._numbers_column_width + padding + 1
@@ -626,7 +641,7 @@ class Syntax(JupyterMixin):
self, console: Console, options: ConsoleOptions
) -> RenderResult:
segments = Segments(self._get_syntax(console, options))
- if self.padding:
+ if any(self.padding):
yield Padding(segments, style=self._get_base_style(), pad=self.padding)
else:
yield segments
@@ -640,15 +655,19 @@ class Syntax(JupyterMixin):
Get the Segments for the Syntax object, excluding any vertical/horizontal padding
"""
transparent_background = self._get_base_style().transparent_background
+ _pad_top, pad_right, _pad_bottom, pad_left = self.padding
+ horizontal_padding = pad_left + pad_right
code_width = (
(
(options.max_width - self._numbers_column_width - 1)
if self.line_numbers
else options.max_width
)
+ - horizontal_padding
if self.code_width is None
else self.code_width
)
+ code_width = max(0, code_width)
ends_on_nl, processed_code = self._process_code(self.code)
text = self.highlight(processed_code, self.line_range)
diff --git a/contrib/python/pip/pip/_vendor/rich/traceback.py b/contrib/python/pip/pip/_vendor/rich/traceback.py
index f82d06f6d13..01b2f5023bb 100644
--- a/contrib/python/pip/pip/_vendor/rich/traceback.py
+++ b/contrib/python/pip/pip/_vendor/rich/traceback.py
@@ -14,6 +14,7 @@ from typing import (
List,
Optional,
Sequence,
+ Set,
Tuple,
Type,
Union,
@@ -177,7 +178,9 @@ def install(
# determine correct tb_offset
compiled = tb_data.get("running_compiled_code", False)
- tb_offset = tb_data.get("tb_offset", 1 if compiled else 0)
+ tb_offset = tb_data.get("tb_offset")
+ if tb_offset is None:
+ tb_offset = 1 if compiled else 0
# remove ipython internal frames from trace with tb_offset
for _ in range(tb_offset):
if tb is None:
@@ -418,6 +421,7 @@ class Traceback:
locals_max_string: int = LOCALS_MAX_STRING,
locals_hide_dunder: bool = True,
locals_hide_sunder: bool = False,
+ _visited_exceptions: Optional[Set[BaseException]] = None,
) -> Trace:
"""Extract traceback information.
@@ -443,6 +447,10 @@ class Traceback:
notes: List[str] = getattr(exc_value, "__notes__", None) or []
+ grouped_exceptions: Set[BaseException] = (
+ set() if _visited_exceptions is None else _visited_exceptions
+ )
+
def safe_str(_object: Any) -> str:
"""Don't allow exceptions from __str__ to propagate."""
try:
@@ -462,6 +470,9 @@ class Traceback:
if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):
stack.is_group = True
for exception in exc_value.exceptions:
+ if exception in grouped_exceptions:
+ continue
+ grouped_exceptions.add(exception)
stack.exceptions.append(
Traceback.extract(
type(exception),
@@ -471,6 +482,7 @@ class Traceback:
locals_max_length=locals_max_length,
locals_hide_dunder=locals_hide_dunder,
locals_hide_sunder=locals_hide_sunder,
+ _visited_exceptions=grouped_exceptions,
)
)
@@ -561,23 +573,26 @@ class Traceback:
if frame_summary.f_locals.get("_rich_traceback_guard", False):
del stack.frames[:]
- cause = getattr(exc_value, "__cause__", None)
- if cause:
- exc_type = cause.__class__
- exc_value = cause
- # __traceback__ can be None, e.g. for exceptions raised by the
- # 'multiprocessing' module
- traceback = cause.__traceback__
- is_cause = True
- continue
+ if not grouped_exceptions:
+ cause = getattr(exc_value, "__cause__", None)
+ if cause is not None and cause is not exc_value:
+ exc_type = cause.__class__
+ exc_value = cause
+ # __traceback__ can be None, e.g. for exceptions raised by the
+ # 'multiprocessing' module
+ traceback = cause.__traceback__
+ is_cause = True
+ continue
- cause = exc_value.__context__
- if cause and not getattr(exc_value, "__suppress_context__", False):
- exc_type = cause.__class__
- exc_value = cause
- traceback = cause.__traceback__
- is_cause = False
- continue
+ cause = exc_value.__context__
+ if cause is not None and not getattr(
+ exc_value, "__suppress_context__", False
+ ):
+ exc_type = cause.__class__
+ exc_value = cause
+ traceback = cause.__traceback__
+ is_cause = False
+ continue
# No cover, code is reached but coverage doesn't recognize it.
break # pragma: no cover
diff --git a/contrib/python/pip/pip/_vendor/truststore/_api.py b/contrib/python/pip/pip/_vendor/truststore/_api.py
index 2c0ce196a36..c63c9cbb6e4 100644
--- a/contrib/python/pip/pip/_vendor/truststore/_api.py
+++ b/contrib/python/pip/pip/_vendor/truststore/_api.py
@@ -22,7 +22,7 @@ else:
from ._openssl import _configure_context, _verify_peercerts_impl
if typing.TYPE_CHECKING:
- from pip._vendor.typing_extensions import Buffer
+ from typing_extensions import Buffer
# From typeshed/stdlib/ssl.pyi
_StrOrBytesPath: typing.TypeAlias = str | bytes | os.PathLike[str] | os.PathLike[bytes]
diff --git a/contrib/python/pip/pip/_vendor/typing_extensions.py b/contrib/python/pip/pip/_vendor/typing_extensions.py
deleted file mode 100644
index da8126b5bf6..00000000000
--- a/contrib/python/pip/pip/_vendor/typing_extensions.py
+++ /dev/null
@@ -1,4584 +0,0 @@
-import abc
-import builtins
-import collections
-import collections.abc
-import contextlib
-import enum
-import functools
-import inspect
-import keyword
-import operator
-import sys
-import types as _types
-import typing
-import warnings
-
-__all__ = [
- # Super-special typing primitives.
- 'Any',
- 'ClassVar',
- 'Concatenate',
- 'Final',
- 'LiteralString',
- 'ParamSpec',
- 'ParamSpecArgs',
- 'ParamSpecKwargs',
- 'Self',
- 'Type',
- 'TypeVar',
- 'TypeVarTuple',
- 'Unpack',
-
- # ABCs (from collections.abc).
- 'Awaitable',
- 'AsyncIterator',
- 'AsyncIterable',
- 'Coroutine',
- 'AsyncGenerator',
- 'AsyncContextManager',
- 'Buffer',
- 'ChainMap',
-
- # Concrete collection types.
- 'ContextManager',
- 'Counter',
- 'Deque',
- 'DefaultDict',
- 'NamedTuple',
- 'OrderedDict',
- 'TypedDict',
-
- # Structural checks, a.k.a. protocols.
- 'SupportsAbs',
- 'SupportsBytes',
- 'SupportsComplex',
- 'SupportsFloat',
- 'SupportsIndex',
- 'SupportsInt',
- 'SupportsRound',
-
- # One-off things.
- 'Annotated',
- 'assert_never',
- 'assert_type',
- 'clear_overloads',
- 'dataclass_transform',
- 'deprecated',
- 'Doc',
- 'evaluate_forward_ref',
- 'get_overloads',
- 'final',
- 'Format',
- 'get_annotations',
- 'get_args',
- 'get_origin',
- 'get_original_bases',
- 'get_protocol_members',
- 'get_type_hints',
- 'IntVar',
- 'is_protocol',
- 'is_typeddict',
- 'Literal',
- 'NewType',
- 'overload',
- 'override',
- 'Protocol',
- 'reveal_type',
- 'runtime',
- 'runtime_checkable',
- 'Text',
- 'TypeAlias',
- 'TypeAliasType',
- 'TypeForm',
- 'TypeGuard',
- 'TypeIs',
- 'TYPE_CHECKING',
- 'Never',
- 'NoReturn',
- 'ReadOnly',
- 'Required',
- 'NotRequired',
- 'NoDefault',
- 'NoExtraItems',
-
- # Pure aliases, have always been in typing
- 'AbstractSet',
- 'AnyStr',
- 'BinaryIO',
- 'Callable',
- 'Collection',
- 'Container',
- 'Dict',
- 'ForwardRef',
- 'FrozenSet',
- 'Generator',
- 'Generic',
- 'Hashable',
- 'IO',
- 'ItemsView',
- 'Iterable',
- 'Iterator',
- 'KeysView',
- 'List',
- 'Mapping',
- 'MappingView',
- 'Match',
- 'MutableMapping',
- 'MutableSequence',
- 'MutableSet',
- 'Optional',
- 'Pattern',
- 'Reversible',
- 'Sequence',
- 'Set',
- 'Sized',
- 'TextIO',
- 'Tuple',
- 'Union',
- 'ValuesView',
- 'cast',
- 'no_type_check',
- 'no_type_check_decorator',
-]
-
-# for backward compatibility
-PEP_560 = True
-GenericMeta = type
-_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta")
-
-# Added with bpo-45166 to 3.10.1+ and some 3.9 versions
-_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__
-
-# The functions below are modified copies of typing internal helpers.
-# They are needed by _ProtocolMeta and they provide support for PEP 646.
-
-
-class _Sentinel:
- def __repr__(self):
- return "<sentinel>"
-
-
-_marker = _Sentinel()
-
-
-if sys.version_info >= (3, 10):
- def _should_collect_from_parameters(t):
- return isinstance(
- t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
- )
-elif sys.version_info >= (3, 9):
- def _should_collect_from_parameters(t):
- return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
-else:
- def _should_collect_from_parameters(t):
- return isinstance(t, typing._GenericAlias) and not t._special
-
-
-NoReturn = typing.NoReturn
-
-# Some unconstrained type variables. These are used by the container types.
-# (These are not for export.)
-T = typing.TypeVar('T') # Any type.
-KT = typing.TypeVar('KT') # Key type.
-VT = typing.TypeVar('VT') # Value type.
-T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers.
-T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant.
-
-
-if sys.version_info >= (3, 11):
- from typing import Any
-else:
-
- class _AnyMeta(type):
- def __instancecheck__(self, obj):
- if self is Any:
- raise TypeError("typing_extensions.Any cannot be used with isinstance()")
- return super().__instancecheck__(obj)
-
- def __repr__(self):
- if self is Any:
- return "typing_extensions.Any"
- return super().__repr__()
-
- class Any(metaclass=_AnyMeta):
- """Special type indicating an unconstrained type.
- - Any is compatible with every type.
- - Any assumed to have all methods.
- - All values assumed to be instances of Any.
- Note that all the above statements are true from the point of view of
- static type checkers. At runtime, Any should not be used with instance
- checks.
- """
- def __new__(cls, *args, **kwargs):
- if cls is Any:
- raise TypeError("Any cannot be instantiated")
- return super().__new__(cls, *args, **kwargs)
-
-
-ClassVar = typing.ClassVar
-
-
-class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
- def __repr__(self):
- return 'typing_extensions.' + self._name
-
-
-Final = typing.Final
-
-if sys.version_info >= (3, 11):
- final = typing.final
-else:
- # @final exists in 3.8+, but we backport it for all versions
- # before 3.11 to keep support for the __final__ attribute.
- # See https://bugs.python.org/issue46342
- def final(f):
- """This decorator can be used to indicate to type checkers that
- the decorated method cannot be overridden, and decorated class
- cannot be subclassed. For example:
-
- class Base:
- @final
- def done(self) -> None:
- ...
- class Sub(Base):
- def done(self) -> None: # Error reported by type checker
- ...
- @final
- class Leaf:
- ...
- class Other(Leaf): # Error reported by type checker
- ...
-
- There is no runtime checking of these properties. The decorator
- sets the ``__final__`` attribute to ``True`` on the decorated object
- to allow runtime introspection.
- """
- try:
- f.__final__ = True
- except (AttributeError, TypeError):
- # Skip the attribute silently if it is not writable.
- # AttributeError happens if the object has __slots__ or a
- # read-only property, TypeError if it's a builtin class.
- pass
- return f
-
-
-def IntVar(name):
- return typing.TypeVar(name)
-
-
-# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8
-if sys.version_info >= (3, 10, 1):
- Literal = typing.Literal
-else:
- def _flatten_literal_params(parameters):
- """An internal helper for Literal creation: flatten Literals among parameters"""
- params = []
- for p in parameters:
- if isinstance(p, _LiteralGenericAlias):
- params.extend(p.__args__)
- else:
- params.append(p)
- return tuple(params)
-
- def _value_and_type_iter(params):
- for p in params:
- yield p, type(p)
-
- class _LiteralGenericAlias(typing._GenericAlias, _root=True):
- def __eq__(self, other):
- if not isinstance(other, _LiteralGenericAlias):
- return NotImplemented
- these_args_deduped = set(_value_and_type_iter(self.__args__))
- other_args_deduped = set(_value_and_type_iter(other.__args__))
- return these_args_deduped == other_args_deduped
-
- def __hash__(self):
- return hash(frozenset(_value_and_type_iter(self.__args__)))
-
- class _LiteralForm(_ExtensionsSpecialForm, _root=True):
- def __init__(self, doc: str):
- self._name = 'Literal'
- self._doc = self.__doc__ = doc
-
- def __getitem__(self, parameters):
- if not isinstance(parameters, tuple):
- parameters = (parameters,)
-
- parameters = _flatten_literal_params(parameters)
-
- val_type_pairs = list(_value_and_type_iter(parameters))
- try:
- deduped_pairs = set(val_type_pairs)
- except TypeError:
- # unhashable parameters
- pass
- else:
- # similar logic to typing._deduplicate on Python 3.9+
- if len(deduped_pairs) < len(val_type_pairs):
- new_parameters = []
- for pair in val_type_pairs:
- if pair in deduped_pairs:
- new_parameters.append(pair[0])
- deduped_pairs.remove(pair)
- assert not deduped_pairs, deduped_pairs
- parameters = tuple(new_parameters)
-
- return _LiteralGenericAlias(self, parameters)
-
- Literal = _LiteralForm(doc="""\
- A type that can be used to indicate to type checkers
- that the corresponding value has a value literally equivalent
- to the provided parameter. For example:
-
- var: Literal[4] = 4
-
- The type checker understands that 'var' is literally equal to
- the value 4 and no other value.
-
- Literal[...] cannot be subclassed. There is no runtime
- checking verifying that the parameter is actually a value
- instead of a type.""")
-
-
-_overload_dummy = typing._overload_dummy
-
-
-if hasattr(typing, "get_overloads"): # 3.11+
- overload = typing.overload
- get_overloads = typing.get_overloads
- clear_overloads = typing.clear_overloads
-else:
- # {module: {qualname: {firstlineno: func}}}
- _overload_registry = collections.defaultdict(
- functools.partial(collections.defaultdict, dict)
- )
-
- def overload(func):
- """Decorator for overloaded functions/methods.
-
- In a stub file, place two or more stub definitions for the same
- function in a row, each decorated with @overload. For example:
-
- @overload
- def utf8(value: None) -> None: ...
- @overload
- def utf8(value: bytes) -> bytes: ...
- @overload
- def utf8(value: str) -> bytes: ...
-
- In a non-stub file (i.e. a regular .py file), do the same but
- follow it with an implementation. The implementation should *not*
- be decorated with @overload. For example:
-
- @overload
- def utf8(value: None) -> None: ...
- @overload
- def utf8(value: bytes) -> bytes: ...
- @overload
- def utf8(value: str) -> bytes: ...
- def utf8(value):
- # implementation goes here
-
- The overloads for a function can be retrieved at runtime using the
- get_overloads() function.
- """
- # classmethod and staticmethod
- f = getattr(func, "__func__", func)
- try:
- _overload_registry[f.__module__][f.__qualname__][
- f.__code__.co_firstlineno
- ] = func
- except AttributeError:
- # Not a normal function; ignore.
- pass
- return _overload_dummy
-
- def get_overloads(func):
- """Return all defined overloads for *func* as a sequence."""
- # classmethod and staticmethod
- f = getattr(func, "__func__", func)
- if f.__module__ not in _overload_registry:
- return []
- mod_dict = _overload_registry[f.__module__]
- if f.__qualname__ not in mod_dict:
- return []
- return list(mod_dict[f.__qualname__].values())
-
- def clear_overloads():
- """Clear all overloads in the registry."""
- _overload_registry.clear()
-
-
-# This is not a real generic class. Don't use outside annotations.
-Type = typing.Type
-
-# Various ABCs mimicking those in collections.abc.
-# A few are simply re-exported for completeness.
-Awaitable = typing.Awaitable
-Coroutine = typing.Coroutine
-AsyncIterable = typing.AsyncIterable
-AsyncIterator = typing.AsyncIterator
-Deque = typing.Deque
-DefaultDict = typing.DefaultDict
-OrderedDict = typing.OrderedDict
-Counter = typing.Counter
-ChainMap = typing.ChainMap
-Text = typing.Text
-TYPE_CHECKING = typing.TYPE_CHECKING
-
-
-if sys.version_info >= (3, 13, 0, "beta"):
- from typing import AsyncContextManager, AsyncGenerator, ContextManager, Generator
-else:
- def _is_dunder(attr):
- return attr.startswith('__') and attr.endswith('__')
-
- # Python <3.9 doesn't have typing._SpecialGenericAlias
- _special_generic_alias_base = getattr(
- typing, "_SpecialGenericAlias", typing._GenericAlias
- )
-
- class _SpecialGenericAlias(_special_generic_alias_base, _root=True):
- def __init__(self, origin, nparams, *, inst=True, name=None, defaults=()):
- if _special_generic_alias_base is typing._GenericAlias:
- # Python <3.9
- self.__origin__ = origin
- self._nparams = nparams
- super().__init__(origin, nparams, special=True, inst=inst, name=name)
- else:
- # Python >= 3.9
- super().__init__(origin, nparams, inst=inst, name=name)
- self._defaults = defaults
-
- def __setattr__(self, attr, val):
- allowed_attrs = {'_name', '_inst', '_nparams', '_defaults'}
- if _special_generic_alias_base is typing._GenericAlias:
- # Python <3.9
- allowed_attrs.add("__origin__")
- if _is_dunder(attr) or attr in allowed_attrs:
- object.__setattr__(self, attr, val)
- else:
- setattr(self.__origin__, attr, val)
-
- @typing._tp_cache
- def __getitem__(self, params):
- if not isinstance(params, tuple):
- params = (params,)
- msg = "Parameters to generic types must be types."
- params = tuple(typing._type_check(p, msg) for p in params)
- if (
- self._defaults
- and len(params) < self._nparams
- and len(params) + len(self._defaults) >= self._nparams
- ):
- params = (*params, *self._defaults[len(params) - self._nparams:])
- actual_len = len(params)
-
- if actual_len != self._nparams:
- if self._defaults:
- expected = f"at least {self._nparams - len(self._defaults)}"
- else:
- expected = str(self._nparams)
- if not self._nparams:
- raise TypeError(f"{self} is not a generic class")
- raise TypeError(
- f"Too {'many' if actual_len > self._nparams else 'few'}"
- f" arguments for {self};"
- f" actual {actual_len}, expected {expected}"
- )
- return self.copy_with(params)
-
- _NoneType = type(None)
- Generator = _SpecialGenericAlias(
- collections.abc.Generator, 3, defaults=(_NoneType, _NoneType)
- )
- AsyncGenerator = _SpecialGenericAlias(
- collections.abc.AsyncGenerator, 2, defaults=(_NoneType,)
- )
- ContextManager = _SpecialGenericAlias(
- contextlib.AbstractContextManager,
- 2,
- name="ContextManager",
- defaults=(typing.Optional[bool],)
- )
- AsyncContextManager = _SpecialGenericAlias(
- contextlib.AbstractAsyncContextManager,
- 2,
- name="AsyncContextManager",
- defaults=(typing.Optional[bool],)
- )
-
-
-_PROTO_ALLOWLIST = {
- 'collections.abc': [
- 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
- 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
- ],
- 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
- 'typing_extensions': ['Buffer'],
-}
-
-
-_EXCLUDED_ATTRS = frozenset(typing.EXCLUDED_ATTRIBUTES) | {
- "__match_args__", "__protocol_attrs__", "__non_callable_proto_members__",
- "__final__",
-}
-
-
-def _get_protocol_attrs(cls):
- attrs = set()
- for base in cls.__mro__[:-1]: # without object
- if base.__name__ in {'Protocol', 'Generic'}:
- continue
- annotations = getattr(base, '__annotations__', {})
- for attr in (*base.__dict__, *annotations):
- if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS):
- attrs.add(attr)
- return attrs
-
-
-def _caller(depth=2):
- try:
- return sys._getframe(depth).f_globals.get('__name__', '__main__')
- except (AttributeError, ValueError): # For platforms without _getframe()
- return None
-
-
-# `__match_args__` attribute was removed from protocol members in 3.13,
-# we want to backport this change to older Python versions.
-if sys.version_info >= (3, 13):
- Protocol = typing.Protocol
-else:
- def _allow_reckless_class_checks(depth=3):
- """Allow instance and class checks for special stdlib modules.
- The abc and functools modules indiscriminately call isinstance() and
- issubclass() on the whole MRO of a user class, which may contain protocols.
- """
- return _caller(depth) in {'abc', 'functools', None}
-
- def _no_init(self, *args, **kwargs):
- if type(self)._is_protocol:
- raise TypeError('Protocols cannot be instantiated')
-
- def _type_check_issubclass_arg_1(arg):
- """Raise TypeError if `arg` is not an instance of `type`
- in `issubclass(arg, <protocol>)`.
-
- In most cases, this is verified by type.__subclasscheck__.
- Checking it again unnecessarily would slow down issubclass() checks,
- so, we don't perform this check unless we absolutely have to.
-
- For various error paths, however,
- we want to ensure that *this* error message is shown to the user
- where relevant, rather than a typing.py-specific error message.
- """
- if not isinstance(arg, type):
- # Same error message as for issubclass(1, int).
- raise TypeError('issubclass() arg 1 must be a class')
-
- # Inheriting from typing._ProtocolMeta isn't actually desirable,
- # but is necessary to allow typing.Protocol and typing_extensions.Protocol
- # to mix without getting TypeErrors about "metaclass conflict"
- class _ProtocolMeta(type(typing.Protocol)):
- # This metaclass is somewhat unfortunate,
- # but is necessary for several reasons...
- #
- # NOTE: DO NOT call super() in any methods in this class
- # That would call the methods on typing._ProtocolMeta on Python 3.8-3.11
- # and those are slow
- def __new__(mcls, name, bases, namespace, **kwargs):
- if name == "Protocol" and len(bases) < 2:
- pass
- elif {Protocol, typing.Protocol} & set(bases):
- for base in bases:
- if not (
- base in {object, typing.Generic, Protocol, typing.Protocol}
- or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
- or is_protocol(base)
- ):
- raise TypeError(
- f"Protocols can only inherit from other protocols, "
- f"got {base!r}"
- )
- return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)
-
- def __init__(cls, *args, **kwargs):
- abc.ABCMeta.__init__(cls, *args, **kwargs)
- if getattr(cls, "_is_protocol", False):
- cls.__protocol_attrs__ = _get_protocol_attrs(cls)
-
- def __subclasscheck__(cls, other):
- if cls is Protocol:
- return type.__subclasscheck__(cls, other)
- if (
- getattr(cls, '_is_protocol', False)
- and not _allow_reckless_class_checks()
- ):
- if not getattr(cls, '_is_runtime_protocol', False):
- _type_check_issubclass_arg_1(other)
- raise TypeError(
- "Instance and class checks can only be used with "
- "@runtime_checkable protocols"
- )
- if (
- # this attribute is set by @runtime_checkable:
- cls.__non_callable_proto_members__
- and cls.__dict__.get("__subclasshook__") is _proto_hook
- ):
- _type_check_issubclass_arg_1(other)
- non_method_attrs = sorted(cls.__non_callable_proto_members__)
- raise TypeError(
- "Protocols with non-method members don't support issubclass()."
- f" Non-method members: {str(non_method_attrs)[1:-1]}."
- )
- return abc.ABCMeta.__subclasscheck__(cls, other)
-
- def __instancecheck__(cls, instance):
- # We need this method for situations where attributes are
- # assigned in __init__.
- if cls is Protocol:
- return type.__instancecheck__(cls, instance)
- if not getattr(cls, "_is_protocol", False):
- # i.e., it's a concrete subclass of a protocol
- return abc.ABCMeta.__instancecheck__(cls, instance)
-
- if (
- not getattr(cls, '_is_runtime_protocol', False) and
- not _allow_reckless_class_checks()
- ):
- raise TypeError("Instance and class checks can only be used with"
- " @runtime_checkable protocols")
-
- if abc.ABCMeta.__instancecheck__(cls, instance):
- return True
-
- for attr in cls.__protocol_attrs__:
- try:
- val = inspect.getattr_static(instance, attr)
- except AttributeError:
- break
- # this attribute is set by @runtime_checkable:
- if val is None and attr not in cls.__non_callable_proto_members__:
- break
- else:
- return True
-
- return False
-
- def __eq__(cls, other):
- # Hack so that typing.Generic.__class_getitem__
- # treats typing_extensions.Protocol
- # as equivalent to typing.Protocol
- if abc.ABCMeta.__eq__(cls, other) is True:
- return True
- return cls is Protocol and other is typing.Protocol
-
- # This has to be defined, or the abc-module cache
- # complains about classes with this metaclass being unhashable,
- # if we define only __eq__!
- def __hash__(cls) -> int:
- return type.__hash__(cls)
-
- @classmethod
- def _proto_hook(cls, other):
- if not cls.__dict__.get('_is_protocol', False):
- return NotImplemented
-
- for attr in cls.__protocol_attrs__:
- for base in other.__mro__:
- # Check if the members appears in the class dictionary...
- if attr in base.__dict__:
- if base.__dict__[attr] is None:
- return NotImplemented
- break
-
- # ...or in annotations, if it is a sub-protocol.
- annotations = getattr(base, '__annotations__', {})
- if (
- isinstance(annotations, collections.abc.Mapping)
- and attr in annotations
- and is_protocol(other)
- ):
- break
- else:
- return NotImplemented
- return True
-
- class Protocol(typing.Generic, metaclass=_ProtocolMeta):
- __doc__ = typing.Protocol.__doc__
- __slots__ = ()
- _is_protocol = True
- _is_runtime_protocol = False
-
- def __init_subclass__(cls, *args, **kwargs):
- super().__init_subclass__(*args, **kwargs)
-
- # Determine if this is a protocol or a concrete subclass.
- if not cls.__dict__.get('_is_protocol', False):
- cls._is_protocol = any(b is Protocol for b in cls.__bases__)
-
- # Set (or override) the protocol subclass hook.
- if '__subclasshook__' not in cls.__dict__:
- cls.__subclasshook__ = _proto_hook
-
- # Prohibit instantiation for protocol classes
- if cls._is_protocol and cls.__init__ is Protocol.__init__:
- cls.__init__ = _no_init
-
-
-if sys.version_info >= (3, 13):
- runtime_checkable = typing.runtime_checkable
-else:
- def runtime_checkable(cls):
- """Mark a protocol class as a runtime protocol.
-
- Such protocol can be used with isinstance() and issubclass().
- Raise TypeError if applied to a non-protocol class.
- This allows a simple-minded structural check very similar to
- one trick ponies in collections.abc such as Iterable.
-
- For example::
-
- @runtime_checkable
- class Closable(Protocol):
- def close(self): ...
-
- assert isinstance(open('/some/file'), Closable)
-
- Warning: this will check only the presence of the required methods,
- not their type signatures!
- """
- if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False):
- raise TypeError(f'@runtime_checkable can be only applied to protocol classes,'
- f' got {cls!r}')
- cls._is_runtime_protocol = True
-
- # typing.Protocol classes on <=3.11 break if we execute this block,
- # because typing.Protocol classes on <=3.11 don't have a
- # `__protocol_attrs__` attribute, and this block relies on the
- # `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+
- # break if we *don't* execute this block, because *they* assume that all
- # protocol classes have a `__non_callable_proto_members__` attribute
- # (which this block sets)
- if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2):
- # PEP 544 prohibits using issubclass()
- # with protocols that have non-method members.
- # See gh-113320 for why we compute this attribute here,
- # rather than in `_ProtocolMeta.__init__`
- cls.__non_callable_proto_members__ = set()
- for attr in cls.__protocol_attrs__:
- try:
- is_callable = callable(getattr(cls, attr, None))
- except Exception as e:
- raise TypeError(
- f"Failed to determine whether protocol member {attr!r} "
- "is a method member"
- ) from e
- else:
- if not is_callable:
- cls.__non_callable_proto_members__.add(attr)
-
- return cls
-
-
-# The "runtime" alias exists for backwards compatibility.
-runtime = runtime_checkable
-
-
-# Our version of runtime-checkable protocols is faster on Python 3.8-3.11
-if sys.version_info >= (3, 12):
- SupportsInt = typing.SupportsInt
- SupportsFloat = typing.SupportsFloat
- SupportsComplex = typing.SupportsComplex
- SupportsBytes = typing.SupportsBytes
- SupportsIndex = typing.SupportsIndex
- SupportsAbs = typing.SupportsAbs
- SupportsRound = typing.SupportsRound
-else:
- @runtime_checkable
- class SupportsInt(Protocol):
- """An ABC with one abstract method __int__."""
- __slots__ = ()
-
- @abc.abstractmethod
- def __int__(self) -> int:
- pass
-
- @runtime_checkable
- class SupportsFloat(Protocol):
- """An ABC with one abstract method __float__."""
- __slots__ = ()
-
- @abc.abstractmethod
- def __float__(self) -> float:
- pass
-
- @runtime_checkable
- class SupportsComplex(Protocol):
- """An ABC with one abstract method __complex__."""
- __slots__ = ()
-
- @abc.abstractmethod
- def __complex__(self) -> complex:
- pass
-
- @runtime_checkable
- class SupportsBytes(Protocol):
- """An ABC with one abstract method __bytes__."""
- __slots__ = ()
-
- @abc.abstractmethod
- def __bytes__(self) -> bytes:
- pass
-
- @runtime_checkable
- class SupportsIndex(Protocol):
- __slots__ = ()
-
- @abc.abstractmethod
- def __index__(self) -> int:
- pass
-
- @runtime_checkable
- class SupportsAbs(Protocol[T_co]):
- """
- An ABC with one abstract method __abs__ that is covariant in its return type.
- """
- __slots__ = ()
-
- @abc.abstractmethod
- def __abs__(self) -> T_co:
- pass
-
- @runtime_checkable
- class SupportsRound(Protocol[T_co]):
- """
- An ABC with one abstract method __round__ that is covariant in its return type.
- """
- __slots__ = ()
-
- @abc.abstractmethod
- def __round__(self, ndigits: int = 0) -> T_co:
- pass
-
-
-def _ensure_subclassable(mro_entries):
- def inner(func):
- if sys.implementation.name == "pypy" and sys.version_info < (3, 9):
- cls_dict = {
- "__call__": staticmethod(func),
- "__mro_entries__": staticmethod(mro_entries)
- }
- t = type(func.__name__, (), cls_dict)
- return functools.update_wrapper(t(), func)
- else:
- func.__mro_entries__ = mro_entries
- return func
- return inner
-
-
-_NEEDS_SINGLETONMETA = (
- not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
-)
-
-if _NEEDS_SINGLETONMETA:
- class SingletonMeta(type):
- def __setattr__(cls, attr, value):
- # TypeError is consistent with the behavior of NoneType
- raise TypeError(
- f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
- )
-
-
-if hasattr(typing, "NoDefault"):
- NoDefault = typing.NoDefault
-else:
- class NoDefaultType(metaclass=SingletonMeta):
- """The type of the NoDefault singleton."""
-
- __slots__ = ()
-
- def __new__(cls):
- return globals().get("NoDefault") or object.__new__(cls)
-
- def __repr__(self):
- return "typing_extensions.NoDefault"
-
- def __reduce__(self):
- return "NoDefault"
-
- NoDefault = NoDefaultType()
- del NoDefaultType
-
-if hasattr(typing, "NoExtraItems"):
- NoExtraItems = typing.NoExtraItems
-else:
- class NoExtraItemsType(metaclass=SingletonMeta):
- """The type of the NoExtraItems singleton."""
-
- __slots__ = ()
-
- def __new__(cls):
- return globals().get("NoExtraItems") or object.__new__(cls)
-
- def __repr__(self):
- return "typing_extensions.NoExtraItems"
-
- def __reduce__(self):
- return "NoExtraItems"
-
- NoExtraItems = NoExtraItemsType()
- del NoExtraItemsType
-
-if _NEEDS_SINGLETONMETA:
- del SingletonMeta
-
-
-# Update this to something like >=3.13.0b1 if and when
-# PEP 728 is implemented in CPython
-_PEP_728_IMPLEMENTED = False
-
-if _PEP_728_IMPLEMENTED:
- # The standard library TypedDict in Python 3.8 does not store runtime information
- # about which (if any) keys are optional. See https://bugs.python.org/issue38834
- # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
- # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059
- # The standard library TypedDict below Python 3.11 does not store runtime
- # information about optional and required keys when using Required or NotRequired.
- # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
- # Aaaand on 3.12 we add __orig_bases__ to TypedDict
- # to enable better runtime introspection.
- # On 3.13 we deprecate some odd ways of creating TypedDicts.
- # Also on 3.13, PEP 705 adds the ReadOnly[] qualifier.
- # PEP 728 (still pending) makes more changes.
- TypedDict = typing.TypedDict
- _TypedDictMeta = typing._TypedDictMeta
- is_typeddict = typing.is_typeddict
-else:
- # 3.10.0 and later
- _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters
-
- def _get_typeddict_qualifiers(annotation_type):
- while True:
- annotation_origin = get_origin(annotation_type)
- if annotation_origin is Annotated:
- annotation_args = get_args(annotation_type)
- if annotation_args:
- annotation_type = annotation_args[0]
- else:
- break
- elif annotation_origin is Required:
- yield Required
- annotation_type, = get_args(annotation_type)
- elif annotation_origin is NotRequired:
- yield NotRequired
- annotation_type, = get_args(annotation_type)
- elif annotation_origin is ReadOnly:
- yield ReadOnly
- annotation_type, = get_args(annotation_type)
- else:
- break
-
- class _TypedDictMeta(type):
-
- def __new__(cls, name, bases, ns, *, total=True, closed=None,
- extra_items=NoExtraItems):
- """Create new typed dict class object.
-
- This method is called when TypedDict is subclassed,
- or when TypedDict is instantiated. This way
- TypedDict supports all three syntax forms described in its docstring.
- Subclasses and instances of TypedDict return actual dictionaries.
- """
- for base in bases:
- if type(base) is not _TypedDictMeta and base is not typing.Generic:
- raise TypeError('cannot inherit from both a TypedDict type '
- 'and a non-TypedDict base class')
- if closed is not None and extra_items is not NoExtraItems:
- raise TypeError(f"Cannot combine closed={closed!r} and extra_items")
-
- if any(issubclass(b, typing.Generic) for b in bases):
- generic_base = (typing.Generic,)
- else:
- generic_base = ()
-
- # typing.py generally doesn't let you inherit from plain Generic, unless
- # the name of the class happens to be "Protocol"
- tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
- tp_dict.__name__ = name
- if tp_dict.__qualname__ == "Protocol":
- tp_dict.__qualname__ = name
-
- if not hasattr(tp_dict, '__orig_bases__'):
- tp_dict.__orig_bases__ = bases
-
- annotations = {}
- if "__annotations__" in ns:
- own_annotations = ns["__annotations__"]
- elif "__annotate__" in ns:
- # TODO: Use inspect.VALUE here, and make the annotations lazily evaluated
- own_annotations = ns["__annotate__"](1)
- else:
- own_annotations = {}
- msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
- if _TAKES_MODULE:
- own_annotations = {
- n: typing._type_check(tp, msg, module=tp_dict.__module__)
- for n, tp in own_annotations.items()
- }
- else:
- own_annotations = {
- n: typing._type_check(tp, msg)
- for n, tp in own_annotations.items()
- }
- required_keys = set()
- optional_keys = set()
- readonly_keys = set()
- mutable_keys = set()
- extra_items_type = extra_items
-
- for base in bases:
- base_dict = base.__dict__
-
- annotations.update(base_dict.get('__annotations__', {}))
- required_keys.update(base_dict.get('__required_keys__', ()))
- optional_keys.update(base_dict.get('__optional_keys__', ()))
- readonly_keys.update(base_dict.get('__readonly_keys__', ()))
- mutable_keys.update(base_dict.get('__mutable_keys__', ()))
-
- # This was specified in an earlier version of PEP 728. Support
- # is retained for backwards compatibility, but only for Python
- # 3.13 and lower.
- if (closed and sys.version_info < (3, 14)
- and "__extra_items__" in own_annotations):
- annotation_type = own_annotations.pop("__extra_items__")
- qualifiers = set(_get_typeddict_qualifiers(annotation_type))
- if Required in qualifiers:
- raise TypeError(
- "Special key __extra_items__ does not support "
- "Required"
- )
- if NotRequired in qualifiers:
- raise TypeError(
- "Special key __extra_items__ does not support "
- "NotRequired"
- )
- extra_items_type = annotation_type
-
- annotations.update(own_annotations)
- for annotation_key, annotation_type in own_annotations.items():
- qualifiers = set(_get_typeddict_qualifiers(annotation_type))
-
- if Required in qualifiers:
- required_keys.add(annotation_key)
- elif NotRequired in qualifiers:
- optional_keys.add(annotation_key)
- elif total:
- required_keys.add(annotation_key)
- else:
- optional_keys.add(annotation_key)
- if ReadOnly in qualifiers:
- mutable_keys.discard(annotation_key)
- readonly_keys.add(annotation_key)
- else:
- mutable_keys.add(annotation_key)
- readonly_keys.discard(annotation_key)
-
- tp_dict.__annotations__ = annotations
- tp_dict.__required_keys__ = frozenset(required_keys)
- tp_dict.__optional_keys__ = frozenset(optional_keys)
- tp_dict.__readonly_keys__ = frozenset(readonly_keys)
- tp_dict.__mutable_keys__ = frozenset(mutable_keys)
- tp_dict.__total__ = total
- tp_dict.__closed__ = closed
- tp_dict.__extra_items__ = extra_items_type
- return tp_dict
-
- __call__ = dict # static method
-
- def __subclasscheck__(cls, other):
- # Typed dicts are only for static structural subtyping.
- raise TypeError('TypedDict does not support instance and class checks')
-
- __instancecheck__ = __subclasscheck__
-
- _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
-
- @_ensure_subclassable(lambda bases: (_TypedDict,))
- def TypedDict(
- typename,
- fields=_marker,
- /,
- *,
- total=True,
- closed=None,
- extra_items=NoExtraItems,
- **kwargs
- ):
- """A simple typed namespace. At runtime it is equivalent to a plain dict.
-
- TypedDict creates a dictionary type such that a type checker will expect all
- instances to have a certain set of keys, where each key is
- associated with a value of a consistent type. This expectation
- is not checked at runtime.
-
- Usage::
-
- class Point2D(TypedDict):
- x: int
- y: int
- label: str
-
- a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
- b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check
-
- assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
-
- The type info can be accessed via the Point2D.__annotations__ dict, and
- the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
- TypedDict supports an additional equivalent form::
-
- Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
-
- By default, all keys must be present in a TypedDict. It is possible
- to override this by specifying totality::
-
- class Point2D(TypedDict, total=False):
- x: int
- y: int
-
- This means that a Point2D TypedDict can have any of the keys omitted. A type
- checker is only expected to support a literal False or True as the value of
- the total argument. True is the default, and makes all items defined in the
- class body be required.
-
- The Required and NotRequired special forms can also be used to mark
- individual keys as being required or not required::
-
- class Point2D(TypedDict):
- x: int # the "x" key must always be present (Required is the default)
- y: NotRequired[int] # the "y" key can be omitted
-
- See PEP 655 for more details on Required and NotRequired.
- """
- if fields is _marker or fields is None:
- if fields is _marker:
- deprecated_thing = "Failing to pass a value for the 'fields' parameter"
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
-
- example = f"`{typename} = TypedDict({typename!r}, {{}})`"
- deprecation_msg = (
- f"{deprecated_thing} is deprecated and will be disallowed in "
- "Python 3.15. To create a TypedDict class with 0 fields "
- "using the functional syntax, pass an empty dictionary, e.g. "
- ) + example + "."
- warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
- # Support a field called "closed"
- if closed is not False and closed is not True and closed is not None:
- kwargs["closed"] = closed
- closed = None
- # Or "extra_items"
- if extra_items is not NoExtraItems:
- kwargs["extra_items"] = extra_items
- extra_items = NoExtraItems
- fields = kwargs
- elif kwargs:
- raise TypeError("TypedDict takes either a dict or keyword arguments,"
- " but not both")
- if kwargs:
- if sys.version_info >= (3, 13):
- raise TypeError("TypedDict takes no keyword arguments")
- warnings.warn(
- "The kwargs-based syntax for TypedDict definitions is deprecated "
- "in Python 3.11, will be removed in Python 3.13, and may not be "
- "understood by third-party type checkers.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- ns = {'__annotations__': dict(fields)}
- module = _caller()
- if module is not None:
- # Setting correct module is necessary to make typed dict classes pickleable.
- ns['__module__'] = module
-
- td = _TypedDictMeta(typename, (), ns, total=total, closed=closed,
- extra_items=extra_items)
- td.__orig_bases__ = (TypedDict,)
- return td
-
- if hasattr(typing, "_TypedDictMeta"):
- _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
- else:
- _TYPEDDICT_TYPES = (_TypedDictMeta,)
-
- def is_typeddict(tp):
- """Check if an annotation is a TypedDict class
-
- For example::
- class Film(TypedDict):
- title: str
- year: int
-
- is_typeddict(Film) # => True
- is_typeddict(Union[list, str]) # => False
- """
- # On 3.8, this would otherwise return True
- if hasattr(typing, "TypedDict") and tp is typing.TypedDict:
- return False
- return isinstance(tp, _TYPEDDICT_TYPES)
-
-
-if hasattr(typing, "assert_type"):
- assert_type = typing.assert_type
-
-else:
- def assert_type(val, typ, /):
- """Assert (to the type checker) that the value is of the given type.
-
- When the type checker encounters a call to assert_type(), it
- emits an error if the value is not of the specified type::
-
- def greet(name: str) -> None:
- assert_type(name, str) # ok
- assert_type(name, int) # type checker error
-
- At runtime this returns the first argument unchanged and otherwise
- does nothing.
- """
- return val
-
-
-if hasattr(typing, "ReadOnly"): # 3.13+
- get_type_hints = typing.get_type_hints
-else: # <=3.13
- # replaces _strip_annotations()
- def _strip_extras(t):
- """Strips Annotated, Required and NotRequired from a given type."""
- if isinstance(t, _AnnotatedAlias):
- return _strip_extras(t.__origin__)
- if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly):
- return _strip_extras(t.__args__[0])
- if isinstance(t, typing._GenericAlias):
- stripped_args = tuple(_strip_extras(a) for a in t.__args__)
- if stripped_args == t.__args__:
- return t
- return t.copy_with(stripped_args)
- if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
- stripped_args = tuple(_strip_extras(a) for a in t.__args__)
- if stripped_args == t.__args__:
- return t
- return _types.GenericAlias(t.__origin__, stripped_args)
- if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
- stripped_args = tuple(_strip_extras(a) for a in t.__args__)
- if stripped_args == t.__args__:
- return t
- return functools.reduce(operator.or_, stripped_args)
-
- return t
-
- def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
- """Return type hints for an object.
-
- This is often the same as obj.__annotations__, but it handles
- forward references encoded as string literals, adds Optional[t] if a
- default value equal to None is set and recursively replaces all
- 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
- (unless 'include_extras=True').
-
- The argument may be a module, class, method, or function. The annotations
- are returned as a dictionary. For classes, annotations include also
- inherited members.
-
- TypeError is raised if the argument is not of a type that can contain
- annotations, and an empty dictionary is returned if no annotations are
- present.
-
- BEWARE -- the behavior of globalns and localns is counterintuitive
- (unless you are familiar with how eval() and exec() work). The
- search order is locals first, then globals.
-
- - If no dict arguments are passed, an attempt is made to use the
- globals from obj (or the respective module's globals for classes),
- and these are also used as the locals. If the object does not appear
- to have globals, an empty dictionary is used.
-
- - If one dict argument is passed, it is used for both globals and
- locals.
-
- - If two dict arguments are passed, they specify globals and
- locals, respectively.
- """
- if hasattr(typing, "Annotated"): # 3.9+
- hint = typing.get_type_hints(
- obj, globalns=globalns, localns=localns, include_extras=True
- )
- else: # 3.8
- hint = typing.get_type_hints(obj, globalns=globalns, localns=localns)
- if sys.version_info < (3, 11):
- _clean_optional(obj, hint, globalns, localns)
- if sys.version_info < (3, 9):
- # In 3.8 eval_type does not flatten Optional[ForwardRef] correctly
- # This will recreate and and cache Unions.
- hint = {
- k: (t
- if get_origin(t) != Union
- else Union[t.__args__])
- for k, t in hint.items()
- }
- if include_extras:
- return hint
- return {k: _strip_extras(t) for k, t in hint.items()}
-
- _NoneType = type(None)
-
- def _could_be_inserted_optional(t):
- """detects Union[..., None] pattern"""
- # 3.8+ compatible checking before _UnionGenericAlias
- if get_origin(t) is not Union:
- return False
- # Assume if last argument is not None they are user defined
- if t.__args__[-1] is not _NoneType:
- return False
- return True
-
- # < 3.11
- def _clean_optional(obj, hints, globalns=None, localns=None):
- # reverts injected Union[..., None] cases from typing.get_type_hints
- # when a None default value is used.
- # see https://github.com/python/typing_extensions/issues/310
- if not hints or isinstance(obj, type):
- return
- defaults = typing._get_defaults(obj) # avoid accessing __annotations___
- if not defaults:
- return
- original_hints = obj.__annotations__
- for name, value in hints.items():
- # Not a Union[..., None] or replacement conditions not fullfilled
- if (not _could_be_inserted_optional(value)
- or name not in defaults
- or defaults[name] is not None
- ):
- continue
- original_value = original_hints[name]
- # value=NoneType should have caused a skip above but check for safety
- if original_value is None:
- original_value = _NoneType
- # Forward reference
- if isinstance(original_value, str):
- if globalns is None:
- if isinstance(obj, _types.ModuleType):
- globalns = obj.__dict__
- else:
- nsobj = obj
- # Find globalns for the unwrapped object.
- while hasattr(nsobj, '__wrapped__'):
- nsobj = nsobj.__wrapped__
- globalns = getattr(nsobj, '__globals__', {})
- if localns is None:
- localns = globalns
- elif localns is None:
- localns = globalns
- if sys.version_info < (3, 9):
- original_value = ForwardRef(original_value)
- else:
- original_value = ForwardRef(
- original_value,
- is_argument=not isinstance(obj, _types.ModuleType)
- )
- original_evaluated = typing._eval_type(original_value, globalns, localns)
- if sys.version_info < (3, 9) and get_origin(original_evaluated) is Union:
- # Union[str, None, "str"] is not reduced to Union[str, None]
- original_evaluated = Union[original_evaluated.__args__]
- # Compare if values differ. Note that even if equal
- # value might be cached by typing._tp_cache contrary to original_evaluated
- if original_evaluated != value or (
- # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias
- hasattr(_types, "UnionType")
- and isinstance(original_evaluated, _types.UnionType)
- and not isinstance(value, _types.UnionType)
- ):
- hints[name] = original_evaluated
-
-# Python 3.9+ has PEP 593 (Annotated)
-if hasattr(typing, 'Annotated'):
- Annotated = typing.Annotated
- # Not exported and not a public API, but needed for get_origin() and get_args()
- # to work.
- _AnnotatedAlias = typing._AnnotatedAlias
-# 3.8
-else:
- class _AnnotatedAlias(typing._GenericAlias, _root=True):
- """Runtime representation of an annotated type.
-
- At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
- with extra annotations. The alias behaves like a normal typing alias,
- instantiating is the same as instantiating the underlying type, binding
- it to types is also the same.
- """
- def __init__(self, origin, metadata):
- if isinstance(origin, _AnnotatedAlias):
- metadata = origin.__metadata__ + metadata
- origin = origin.__origin__
- super().__init__(origin, origin)
- self.__metadata__ = metadata
-
- def copy_with(self, params):
- assert len(params) == 1
- new_type = params[0]
- return _AnnotatedAlias(new_type, self.__metadata__)
-
- def __repr__(self):
- return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, "
- f"{', '.join(repr(a) for a in self.__metadata__)}]")
-
- def __reduce__(self):
- return operator.getitem, (
- Annotated, (self.__origin__, *self.__metadata__)
- )
-
- def __eq__(self, other):
- if not isinstance(other, _AnnotatedAlias):
- return NotImplemented
- if self.__origin__ != other.__origin__:
- return False
- return self.__metadata__ == other.__metadata__
-
- def __hash__(self):
- return hash((self.__origin__, self.__metadata__))
-
- class Annotated:
- """Add context specific metadata to a type.
-
- Example: Annotated[int, runtime_check.Unsigned] indicates to the
- hypothetical runtime_check module that this type is an unsigned int.
- Every other consumer of this type can ignore this metadata and treat
- this type as int.
-
- The first argument to Annotated must be a valid type (and will be in
- the __origin__ field), the remaining arguments are kept as a tuple in
- the __extra__ field.
-
- Details:
-
- - It's an error to call `Annotated` with less than two arguments.
- - Nested Annotated are flattened::
-
- Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]
-
- - Instantiating an annotated type is equivalent to instantiating the
- underlying type::
-
- Annotated[C, Ann1](5) == C(5)
-
- - Annotated can be used as a generic type alias::
-
- Optimized = Annotated[T, runtime.Optimize()]
- Optimized[int] == Annotated[int, runtime.Optimize()]
-
- OptimizedList = Annotated[List[T], runtime.Optimize()]
- OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
- """
-
- __slots__ = ()
-
- def __new__(cls, *args, **kwargs):
- raise TypeError("Type Annotated cannot be instantiated.")
-
- @typing._tp_cache
- def __class_getitem__(cls, params):
- if not isinstance(params, tuple) or len(params) < 2:
- raise TypeError("Annotated[...] should be used "
- "with at least two arguments (a type and an "
- "annotation).")
- allowed_special_forms = (ClassVar, Final)
- if get_origin(params[0]) in allowed_special_forms:
- origin = params[0]
- else:
- msg = "Annotated[t, ...]: t must be a type."
- origin = typing._type_check(params[0], msg)
- metadata = tuple(params[1:])
- return _AnnotatedAlias(origin, metadata)
-
- def __init_subclass__(cls, *args, **kwargs):
- raise TypeError(
- f"Cannot subclass {cls.__module__}.Annotated"
- )
-
-# Python 3.8 has get_origin() and get_args() but those implementations aren't
-# Annotated-aware, so we can't use those. Python 3.9's versions don't support
-# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
-if sys.version_info[:2] >= (3, 10):
- get_origin = typing.get_origin
- get_args = typing.get_args
-# 3.8-3.9
-else:
- try:
- # 3.9+
- from typing import _BaseGenericAlias
- except ImportError:
- _BaseGenericAlias = typing._GenericAlias
- try:
- # 3.9+
- from typing import GenericAlias as _typing_GenericAlias
- except ImportError:
- _typing_GenericAlias = typing._GenericAlias
-
- def get_origin(tp):
- """Get the unsubscripted version of a type.
-
- This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
- and Annotated. Return None for unsupported types. Examples::
-
- get_origin(Literal[42]) is Literal
- get_origin(int) is None
- get_origin(ClassVar[int]) is ClassVar
- get_origin(Generic) is Generic
- get_origin(Generic[T]) is Generic
- get_origin(Union[T, int]) is Union
- get_origin(List[Tuple[T, T]][int]) == list
- get_origin(P.args) is P
- """
- if isinstance(tp, _AnnotatedAlias):
- return Annotated
- if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias,
- ParamSpecArgs, ParamSpecKwargs)):
- return tp.__origin__
- if tp is typing.Generic:
- return typing.Generic
- return None
-
- def get_args(tp):
- """Get type arguments with all substitutions performed.
-
- For unions, basic simplifications used by Union constructor are performed.
- Examples::
- get_args(Dict[str, int]) == (str, int)
- get_args(int) == ()
- get_args(Union[int, Union[T, int], str][int]) == (int, str)
- get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
- get_args(Callable[[], T][int]) == ([], int)
- """
- if isinstance(tp, _AnnotatedAlias):
- return (tp.__origin__, *tp.__metadata__)
- if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)):
- if getattr(tp, "_special", False):
- return ()
- res = tp.__args__
- if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
- res = (list(res[:-1]), res[-1])
- return res
- return ()
-
-
-# 3.10+
-if hasattr(typing, 'TypeAlias'):
- TypeAlias = typing.TypeAlias
-# 3.9
-elif sys.version_info[:2] >= (3, 9):
- @_ExtensionsSpecialForm
- def TypeAlias(self, parameters):
- """Special marker indicating that an assignment should
- be recognized as a proper type alias definition by type
- checkers.
-
- For example::
-
- Predicate: TypeAlias = Callable[..., bool]
-
- It's invalid when used anywhere except as in the example above.
- """
- raise TypeError(f"{self} is not subscriptable")
-# 3.8
-else:
- TypeAlias = _ExtensionsSpecialForm(
- 'TypeAlias',
- doc="""Special marker indicating that an assignment should
- be recognized as a proper type alias definition by type
- checkers.
-
- For example::
-
- Predicate: TypeAlias = Callable[..., bool]
-
- It's invalid when used anywhere except as in the example
- above."""
- )
-
-
-def _set_default(type_param, default):
- type_param.has_default = lambda: default is not NoDefault
- type_param.__default__ = default
-
-
-def _set_module(typevarlike):
- # for pickling:
- def_mod = _caller(depth=3)
- if def_mod != 'typing_extensions':
- typevarlike.__module__ = def_mod
-
-
-class _DefaultMixin:
- """Mixin for TypeVarLike defaults."""
-
- __slots__ = ()
- __init__ = _set_default
-
-
-# Classes using this metaclass must provide a _backported_typevarlike ClassVar
-class _TypeVarLikeMeta(type):
- def __instancecheck__(cls, __instance: Any) -> bool:
- return isinstance(__instance, cls._backported_typevarlike)
-
-
-if _PEP_696_IMPLEMENTED:
- from typing import TypeVar
-else:
- # Add default and infer_variance parameters from PEP 696 and 695
- class TypeVar(metaclass=_TypeVarLikeMeta):
- """Type variable."""
-
- _backported_typevarlike = typing.TypeVar
-
- def __new__(cls, name, *constraints, bound=None,
- covariant=False, contravariant=False,
- default=NoDefault, infer_variance=False):
- if hasattr(typing, "TypeAliasType"):
- # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
- typevar = typing.TypeVar(name, *constraints, bound=bound,
- covariant=covariant, contravariant=contravariant,
- infer_variance=infer_variance)
- else:
- typevar = typing.TypeVar(name, *constraints, bound=bound,
- covariant=covariant, contravariant=contravariant)
- if infer_variance and (covariant or contravariant):
- raise ValueError("Variance cannot be specified with infer_variance.")
- typevar.__infer_variance__ = infer_variance
-
- _set_default(typevar, default)
- _set_module(typevar)
-
- def _tvar_prepare_subst(alias, args):
- if (
- typevar.has_default()
- and alias.__parameters__.index(typevar) == len(args)
- ):
- args += (typevar.__default__,)
- return args
-
- typevar.__typing_prepare_subst__ = _tvar_prepare_subst
- return typevar
-
- def __init_subclass__(cls) -> None:
- raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type")
-
-
-# Python 3.10+ has PEP 612
-if hasattr(typing, 'ParamSpecArgs'):
- ParamSpecArgs = typing.ParamSpecArgs
- ParamSpecKwargs = typing.ParamSpecKwargs
-# 3.8-3.9
-else:
- class _Immutable:
- """Mixin to indicate that object should not be copied."""
- __slots__ = ()
-
- def __copy__(self):
- return self
-
- def __deepcopy__(self, memo):
- return self
-
- class ParamSpecArgs(_Immutable):
- """The args for a ParamSpec object.
-
- Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
-
- ParamSpecArgs objects have a reference back to their ParamSpec:
-
- P.args.__origin__ is P
-
- This type is meant for runtime introspection and has no special meaning to
- static type checkers.
- """
- def __init__(self, origin):
- self.__origin__ = origin
-
- def __repr__(self):
- return f"{self.__origin__.__name__}.args"
-
- def __eq__(self, other):
- if not isinstance(other, ParamSpecArgs):
- return NotImplemented
- return self.__origin__ == other.__origin__
-
- class ParamSpecKwargs(_Immutable):
- """The kwargs for a ParamSpec object.
-
- Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.
-
- ParamSpecKwargs objects have a reference back to their ParamSpec:
-
- P.kwargs.__origin__ is P
-
- This type is meant for runtime introspection and has no special meaning to
- static type checkers.
- """
- def __init__(self, origin):
- self.__origin__ = origin
-
- def __repr__(self):
- return f"{self.__origin__.__name__}.kwargs"
-
- def __eq__(self, other):
- if not isinstance(other, ParamSpecKwargs):
- return NotImplemented
- return self.__origin__ == other.__origin__
-
-
-if _PEP_696_IMPLEMENTED:
- from typing import ParamSpec
-
-# 3.10+
-elif hasattr(typing, 'ParamSpec'):
-
- # Add default parameter - PEP 696
- class ParamSpec(metaclass=_TypeVarLikeMeta):
- """Parameter specification."""
-
- _backported_typevarlike = typing.ParamSpec
-
- def __new__(cls, name, *, bound=None,
- covariant=False, contravariant=False,
- infer_variance=False, default=NoDefault):
- if hasattr(typing, "TypeAliasType"):
- # PEP 695 implemented, can pass infer_variance to typing.TypeVar
- paramspec = typing.ParamSpec(name, bound=bound,
- covariant=covariant,
- contravariant=contravariant,
- infer_variance=infer_variance)
- else:
- paramspec = typing.ParamSpec(name, bound=bound,
- covariant=covariant,
- contravariant=contravariant)
- paramspec.__infer_variance__ = infer_variance
-
- _set_default(paramspec, default)
- _set_module(paramspec)
-
- def _paramspec_prepare_subst(alias, args):
- params = alias.__parameters__
- i = params.index(paramspec)
- if i == len(args) and paramspec.has_default():
- args = [*args, paramspec.__default__]
- if i >= len(args):
- raise TypeError(f"Too few arguments for {alias}")
- # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
- if len(params) == 1 and not typing._is_param_expr(args[0]):
- assert i == 0
- args = (args,)
- # Convert lists to tuples to help other libraries cache the results.
- elif isinstance(args[i], list):
- args = (*args[:i], tuple(args[i]), *args[i + 1:])
- return args
-
- paramspec.__typing_prepare_subst__ = _paramspec_prepare_subst
- return paramspec
-
- def __init_subclass__(cls) -> None:
- raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")
-
-# 3.8-3.9
-else:
-
- # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
- class ParamSpec(list, _DefaultMixin):
- """Parameter specification variable.
-
- Usage::
-
- P = ParamSpec('P')
-
- Parameter specification variables exist primarily for the benefit of static
- type checkers. They are used to forward the parameter types of one
- callable to another callable, a pattern commonly found in higher order
- functions and decorators. They are only valid when used in ``Concatenate``,
- or s the first argument to ``Callable``. In Python 3.10 and higher,
- they are also supported in user-defined Generics at runtime.
- See class Generic for more information on generic types. An
- example for annotating a decorator::
-
- T = TypeVar('T')
- P = ParamSpec('P')
-
- def add_logging(f: Callable[P, T]) -> Callable[P, T]:
- '''A type-safe decorator to add logging to a function.'''
- def inner(*args: P.args, **kwargs: P.kwargs) -> T:
- logging.info(f'{f.__name__} was called')
- return f(*args, **kwargs)
- return inner
-
- @add_logging
- def add_two(x: float, y: float) -> float:
- '''Add two numbers together.'''
- return x + y
-
- Parameter specification variables defined with covariant=True or
- contravariant=True can be used to declare covariant or contravariant
- generic types. These keyword arguments are valid, but their actual semantics
- are yet to be decided. See PEP 612 for details.
-
- Parameter specification variables can be introspected. e.g.:
-
- P.__name__ == 'T'
- P.__bound__ == None
- P.__covariant__ == False
- P.__contravariant__ == False
-
- Note that only parameter specification variables defined in global scope can
- be pickled.
- """
-
- # Trick Generic __parameters__.
- __class__ = typing.TypeVar
-
- @property
- def args(self):
- return ParamSpecArgs(self)
-
- @property
- def kwargs(self):
- return ParamSpecKwargs(self)
-
- def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
- infer_variance=False, default=NoDefault):
- list.__init__(self, [self])
- self.__name__ = name
- self.__covariant__ = bool(covariant)
- self.__contravariant__ = bool(contravariant)
- self.__infer_variance__ = bool(infer_variance)
- if bound:
- self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
- else:
- self.__bound__ = None
- _DefaultMixin.__init__(self, default)
-
- # for pickling:
- def_mod = _caller()
- if def_mod != 'typing_extensions':
- self.__module__ = def_mod
-
- def __repr__(self):
- if self.__infer_variance__:
- prefix = ''
- elif self.__covariant__:
- prefix = '+'
- elif self.__contravariant__:
- prefix = '-'
- else:
- prefix = '~'
- return prefix + self.__name__
-
- def __hash__(self):
- return object.__hash__(self)
-
- def __eq__(self, other):
- return self is other
-
- def __reduce__(self):
- return self.__name__
-
- # Hack to get typing._type_check to pass.
- def __call__(self, *args, **kwargs):
- pass
-
-
-# 3.8-3.9
-if not hasattr(typing, 'Concatenate'):
- # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
-
- # 3.9.0-1
- if not hasattr(typing, '_type_convert'):
- def _type_convert(arg, module=None, *, allow_special_forms=False):
- """For converting None to type(None), and strings to ForwardRef."""
- if arg is None:
- return type(None)
- if isinstance(arg, str):
- if sys.version_info <= (3, 9, 6):
- return ForwardRef(arg)
- if sys.version_info <= (3, 9, 7):
- return ForwardRef(arg, module=module)
- return ForwardRef(arg, module=module, is_class=allow_special_forms)
- return arg
- else:
- _type_convert = typing._type_convert
-
- class _ConcatenateGenericAlias(list):
-
- # Trick Generic into looking into this for __parameters__.
- __class__ = typing._GenericAlias
-
- # Flag in 3.8.
- _special = False
-
- def __init__(self, origin, args):
- super().__init__(args)
- self.__origin__ = origin
- self.__args__ = args
-
- def __repr__(self):
- _type_repr = typing._type_repr
- return (f'{_type_repr(self.__origin__)}'
- f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')
-
- def __hash__(self):
- return hash((self.__origin__, self.__args__))
-
- # Hack to get typing._type_check to pass in Generic.
- def __call__(self, *args, **kwargs):
- pass
-
- @property
- def __parameters__(self):
- return tuple(
- tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
- )
-
- # 3.8; needed for typing._subst_tvars
- # 3.9 used by __getitem__ below
- def copy_with(self, params):
- if isinstance(params[-1], _ConcatenateGenericAlias):
- params = (*params[:-1], *params[-1].__args__)
- elif isinstance(params[-1], (list, tuple)):
- return (*params[:-1], *params[-1])
- elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))):
- raise TypeError("The last parameter to Concatenate should be a "
- "ParamSpec variable or ellipsis.")
- return self.__class__(self.__origin__, params)
-
- # 3.9; accessed during GenericAlias.__getitem__ when substituting
- def __getitem__(self, args):
- if self.__origin__ in (Generic, Protocol):
- # Can't subscript Generic[...] or Protocol[...].
- raise TypeError(f"Cannot subscript already-subscripted {self}")
- if not self.__parameters__:
- raise TypeError(f"{self} is not a generic class")
-
- if not isinstance(args, tuple):
- args = (args,)
- args = _unpack_args(*(_type_convert(p) for p in args))
- params = self.__parameters__
- for param in params:
- prepare = getattr(param, "__typing_prepare_subst__", None)
- if prepare is not None:
- args = prepare(self, args)
- # 3.8 - 3.9 & typing.ParamSpec
- elif isinstance(param, ParamSpec):
- i = params.index(param)
- if (
- i == len(args)
- and getattr(param, '__default__', NoDefault) is not NoDefault
- ):
- args = [*args, param.__default__]
- if i >= len(args):
- raise TypeError(f"Too few arguments for {self}")
- # Special case for Z[[int, str, bool]] == Z[int, str, bool]
- if len(params) == 1 and not _is_param_expr(args[0]):
- assert i == 0
- args = (args,)
- elif (
- isinstance(args[i], list)
- # 3.8 - 3.9
- # This class inherits from list do not convert
- and not isinstance(args[i], _ConcatenateGenericAlias)
- ):
- args = (*args[:i], tuple(args[i]), *args[i + 1:])
-
- alen = len(args)
- plen = len(params)
- if alen != plen:
- raise TypeError(
- f"Too {'many' if alen > plen else 'few'} arguments for {self};"
- f" actual {alen}, expected {plen}"
- )
-
- subst = dict(zip(self.__parameters__, args))
- # determine new args
- new_args = []
- for arg in self.__args__:
- if isinstance(arg, type):
- new_args.append(arg)
- continue
- if isinstance(arg, TypeVar):
- arg = subst[arg]
- if (
- (isinstance(arg, typing._GenericAlias) and _is_unpack(arg))
- or (
- hasattr(_types, "GenericAlias")
- and isinstance(arg, _types.GenericAlias)
- and getattr(arg, "__unpacked__", False)
- )
- ):
- raise TypeError(f"{arg} is not valid as type argument")
-
- elif isinstance(arg,
- typing._GenericAlias
- if not hasattr(_types, "GenericAlias") else
- (typing._GenericAlias, _types.GenericAlias)
- ):
- subparams = arg.__parameters__
- if subparams:
- subargs = tuple(subst[x] for x in subparams)
- arg = arg[subargs]
- new_args.append(arg)
- return self.copy_with(tuple(new_args))
-
-# 3.10+
-else:
- _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
-
- # 3.10
- if sys.version_info < (3, 11):
-
- class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True):
- # needed for checks in collections.abc.Callable to accept this class
- __module__ = "typing"
-
- def copy_with(self, params):
- if isinstance(params[-1], (list, tuple)):
- return (*params[:-1], *params[-1])
- if isinstance(params[-1], typing._ConcatenateGenericAlias):
- params = (*params[:-1], *params[-1].__args__)
- elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
- raise TypeError("The last parameter to Concatenate should be a "
- "ParamSpec variable or ellipsis.")
- return super(typing._ConcatenateGenericAlias, self).copy_with(params)
-
- def __getitem__(self, args):
- value = super().__getitem__(args)
- if isinstance(value, tuple) and any(_is_unpack(t) for t in value):
- return tuple(_unpack_args(*(n for n in value)))
- return value
-
-
-# 3.8-3.9.2
-class _EllipsisDummy: ...
-
-
-# 3.8-3.10
-def _create_concatenate_alias(origin, parameters):
- if parameters[-1] is ... and sys.version_info < (3, 9, 2):
- # Hack: Arguments must be types, replace it with one.
- parameters = (*parameters[:-1], _EllipsisDummy)
- if sys.version_info >= (3, 10, 3):
- concatenate = _ConcatenateGenericAlias(origin, parameters,
- _typevar_types=(TypeVar, ParamSpec),
- _paramspec_tvars=True)
- else:
- concatenate = _ConcatenateGenericAlias(origin, parameters)
- if parameters[-1] is not _EllipsisDummy:
- return concatenate
- # Remove dummy again
- concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ...
- for p in concatenate.__args__)
- if sys.version_info < (3, 10):
- # backport needs __args__ adjustment only
- return concatenate
- concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
- if p is not _EllipsisDummy)
- return concatenate
-
-
-# 3.8-3.10
-@typing._tp_cache
-def _concatenate_getitem(self, parameters):
- if parameters == ():
- raise TypeError("Cannot take a Concatenate of no types.")
- if not isinstance(parameters, tuple):
- parameters = (parameters,)
- if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
- raise TypeError("The last parameter to Concatenate should be a "
- "ParamSpec variable or ellipsis.")
- msg = "Concatenate[arg, ...]: each arg must be a type."
- parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
- parameters[-1])
- return _create_concatenate_alias(self, parameters)
-
-
-# 3.11+; Concatenate does not accept ellipsis in 3.10
-if sys.version_info >= (3, 11):
- Concatenate = typing.Concatenate
-# 3.9-3.10
-elif sys.version_info[:2] >= (3, 9):
- @_ExtensionsSpecialForm
- def Concatenate(self, parameters):
- """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
- higher order function which adds, removes or transforms parameters of a
- callable.
-
- For example::
-
- Callable[Concatenate[int, P], int]
-
- See PEP 612 for detailed information.
- """
- return _concatenate_getitem(self, parameters)
-# 3.8
-else:
- class _ConcatenateForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- return _concatenate_getitem(self, parameters)
-
- Concatenate = _ConcatenateForm(
- 'Concatenate',
- doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
- higher order function which adds, removes or transforms parameters of a
- callable.
-
- For example::
-
- Callable[Concatenate[int, P], int]
-
- See PEP 612 for detailed information.
- """)
-
-# 3.10+
-if hasattr(typing, 'TypeGuard'):
- TypeGuard = typing.TypeGuard
-# 3.9
-elif sys.version_info[:2] >= (3, 9):
- @_ExtensionsSpecialForm
- def TypeGuard(self, parameters):
- """Special typing form used to annotate the return type of a user-defined
- type guard function. ``TypeGuard`` only accepts a single type argument.
- At runtime, functions marked this way should return a boolean.
-
- ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
- type checkers to determine a more precise type of an expression within a
- program's code flow. Usually type narrowing is done by analyzing
- conditional code flow and applying the narrowing to a block of code. The
- conditional expression here is sometimes referred to as a "type guard".
-
- Sometimes it would be convenient to use a user-defined boolean function
- as a type guard. Such a function should use ``TypeGuard[...]`` as its
- return type to alert static type checkers to this intention.
-
- Using ``-> TypeGuard`` tells the static type checker that for a given
- function:
-
- 1. The return value is a boolean.
- 2. If the return value is ``True``, the type of its argument
- is the type inside ``TypeGuard``.
-
- For example::
-
- def is_str(val: Union[str, float]):
- # "isinstance" type guard
- if isinstance(val, str):
- # Type of ``val`` is narrowed to ``str``
- ...
- else:
- # Else, type of ``val`` is narrowed to ``float``.
- ...
-
- Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
- form of ``TypeA`` (it can even be a wider form) and this may lead to
- type-unsafe results. The main reason is to allow for things like
- narrowing ``List[object]`` to ``List[str]`` even though the latter is not
- a subtype of the former, since ``List`` is invariant. The responsibility of
- writing type-safe type guards is left to the user.
-
- ``TypeGuard`` also works with type variables. For more information, see
- PEP 647 (User-Defined Type Guards).
- """
- item = typing._type_check(parameters, f'{self} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-# 3.8
-else:
- class _TypeGuardForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- item = typing._type_check(parameters,
- f'{self._name} accepts only a single type')
- return typing._GenericAlias(self, (item,))
-
- TypeGuard = _TypeGuardForm(
- 'TypeGuard',
- doc="""Special typing form used to annotate the return type of a user-defined
- type guard function. ``TypeGuard`` only accepts a single type argument.
- At runtime, functions marked this way should return a boolean.
-
- ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
- type checkers to determine a more precise type of an expression within a
- program's code flow. Usually type narrowing is done by analyzing
- conditional code flow and applying the narrowing to a block of code. The
- conditional expression here is sometimes referred to as a "type guard".
-
- Sometimes it would be convenient to use a user-defined boolean function
- as a type guard. Such a function should use ``TypeGuard[...]`` as its
- return type to alert static type checkers to this intention.
-
- Using ``-> TypeGuard`` tells the static type checker that for a given
- function:
-
- 1. The return value is a boolean.
- 2. If the return value is ``True``, the type of its argument
- is the type inside ``TypeGuard``.
-
- For example::
-
- def is_str(val: Union[str, float]):
- # "isinstance" type guard
- if isinstance(val, str):
- # Type of ``val`` is narrowed to ``str``
- ...
- else:
- # Else, type of ``val`` is narrowed to ``float``.
- ...
-
- Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
- form of ``TypeA`` (it can even be a wider form) and this may lead to
- type-unsafe results. The main reason is to allow for things like
- narrowing ``List[object]`` to ``List[str]`` even though the latter is not
- a subtype of the former, since ``List`` is invariant. The responsibility of
- writing type-safe type guards is left to the user.
-
- ``TypeGuard`` also works with type variables. For more information, see
- PEP 647 (User-Defined Type Guards).
- """)
-
-# 3.13+
-if hasattr(typing, 'TypeIs'):
- TypeIs = typing.TypeIs
-# 3.9
-elif sys.version_info[:2] >= (3, 9):
- @_ExtensionsSpecialForm
- def TypeIs(self, parameters):
- """Special typing form used to annotate the return type of a user-defined
- type narrower function. ``TypeIs`` only accepts a single type argument.
- At runtime, functions marked this way should return a boolean.
-
- ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static
- type checkers to determine a more precise type of an expression within a
- program's code flow. Usually type narrowing is done by analyzing
- conditional code flow and applying the narrowing to a block of code. The
- conditional expression here is sometimes referred to as a "type guard".
-
- Sometimes it would be convenient to use a user-defined boolean function
- as a type guard. Such a function should use ``TypeIs[...]`` as its
- return type to alert static type checkers to this intention.
-
- Using ``-> TypeIs`` tells the static type checker that for a given
- function:
-
- 1. The return value is a boolean.
- 2. If the return value is ``True``, the type of its argument
- is the intersection of the type inside ``TypeIs`` and the argument's
- previously known type.
-
- For example::
-
- def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]:
- return hasattr(val, '__await__')
-
- def f(val: Union[int, Awaitable[int]]) -> int:
- if is_awaitable(val):
- assert_type(val, Awaitable[int])
- else:
- assert_type(val, int)
-
- ``TypeIs`` also works with type variables. For more information, see
- PEP 742 (Narrowing types with TypeIs).
- """
- item = typing._type_check(parameters, f'{self} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-# 3.8
-else:
- class _TypeIsForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- item = typing._type_check(parameters,
- f'{self._name} accepts only a single type')
- return typing._GenericAlias(self, (item,))
-
- TypeIs = _TypeIsForm(
- 'TypeIs',
- doc="""Special typing form used to annotate the return type of a user-defined
- type narrower function. ``TypeIs`` only accepts a single type argument.
- At runtime, functions marked this way should return a boolean.
-
- ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static
- type checkers to determine a more precise type of an expression within a
- program's code flow. Usually type narrowing is done by analyzing
- conditional code flow and applying the narrowing to a block of code. The
- conditional expression here is sometimes referred to as a "type guard".
-
- Sometimes it would be convenient to use a user-defined boolean function
- as a type guard. Such a function should use ``TypeIs[...]`` as its
- return type to alert static type checkers to this intention.
-
- Using ``-> TypeIs`` tells the static type checker that for a given
- function:
-
- 1. The return value is a boolean.
- 2. If the return value is ``True``, the type of its argument
- is the intersection of the type inside ``TypeIs`` and the argument's
- previously known type.
-
- For example::
-
- def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]:
- return hasattr(val, '__await__')
-
- def f(val: Union[int, Awaitable[int]]) -> int:
- if is_awaitable(val):
- assert_type(val, Awaitable[int])
- else:
- assert_type(val, int)
-
- ``TypeIs`` also works with type variables. For more information, see
- PEP 742 (Narrowing types with TypeIs).
- """)
-
-# 3.14+?
-if hasattr(typing, 'TypeForm'):
- TypeForm = typing.TypeForm
-# 3.9
-elif sys.version_info[:2] >= (3, 9):
- class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
- # TypeForm(X) is equivalent to X but indicates to the type checker
- # that the object is a TypeForm.
- def __call__(self, obj, /):
- return obj
-
- @_TypeFormForm
- def TypeForm(self, parameters):
- """A special form representing the value that results from the evaluation
- of a type expression. This value encodes the information supplied in the
- type expression, and it represents the type described by that type expression.
-
- When used in a type expression, TypeForm describes a set of type form objects.
- It accepts a single type argument, which must be a valid type expression.
- ``TypeForm[T]`` describes the set of all type form objects that represent
- the type T or types that are assignable to T.
-
- Usage:
-
- def cast[T](typ: TypeForm[T], value: Any) -> T: ...
-
- reveal_type(cast(int, "x")) # int
-
- See PEP 747 for more information.
- """
- item = typing._type_check(parameters, f'{self} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-# 3.8
-else:
- class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- item = typing._type_check(parameters,
- f'{self._name} accepts only a single type')
- return typing._GenericAlias(self, (item,))
-
- def __call__(self, obj, /):
- return obj
-
- TypeForm = _TypeFormForm(
- 'TypeForm',
- doc="""A special form representing the value that results from the evaluation
- of a type expression. This value encodes the information supplied in the
- type expression, and it represents the type described by that type expression.
-
- When used in a type expression, TypeForm describes a set of type form objects.
- It accepts a single type argument, which must be a valid type expression.
- ``TypeForm[T]`` describes the set of all type form objects that represent
- the type T or types that are assignable to T.
-
- Usage:
-
- def cast[T](typ: TypeForm[T], value: Any) -> T: ...
-
- reveal_type(cast(int, "x")) # int
-
- See PEP 747 for more information.
- """)
-
-
-# Vendored from cpython typing._SpecialFrom
-class _SpecialForm(typing._Final, _root=True):
- __slots__ = ('_name', '__doc__', '_getitem')
-
- def __init__(self, getitem):
- self._getitem = getitem
- self._name = getitem.__name__
- self.__doc__ = getitem.__doc__
-
- def __getattr__(self, item):
- if item in {'__name__', '__qualname__'}:
- return self._name
-
- raise AttributeError(item)
-
- def __mro_entries__(self, bases):
- raise TypeError(f"Cannot subclass {self!r}")
-
- def __repr__(self):
- return f'typing_extensions.{self._name}'
-
- def __reduce__(self):
- return self._name
-
- def __call__(self, *args, **kwds):
- raise TypeError(f"Cannot instantiate {self!r}")
-
- def __or__(self, other):
- return typing.Union[self, other]
-
- def __ror__(self, other):
- return typing.Union[other, self]
-
- def __instancecheck__(self, obj):
- raise TypeError(f"{self} cannot be used with isinstance()")
-
- def __subclasscheck__(self, cls):
- raise TypeError(f"{self} cannot be used with issubclass()")
-
- @typing._tp_cache
- def __getitem__(self, parameters):
- return self._getitem(self, parameters)
-
-
-if hasattr(typing, "LiteralString"): # 3.11+
- LiteralString = typing.LiteralString
-else:
- @_SpecialForm
- def LiteralString(self, params):
- """Represents an arbitrary literal string.
-
- Example::
-
- from pip._vendor.typing_extensions import LiteralString
-
- def query(sql: LiteralString) -> ...:
- ...
-
- query("SELECT * FROM table") # ok
- query(f"SELECT * FROM {input()}") # not ok
-
- See PEP 675 for details.
-
- """
- raise TypeError(f"{self} is not subscriptable")
-
-
-if hasattr(typing, "Self"): # 3.11+
- Self = typing.Self
-else:
- @_SpecialForm
- def Self(self, params):
- """Used to spell the type of "self" in classes.
-
- Example::
-
- from typing import Self
-
- class ReturnsSelf:
- def parse(self, data: bytes) -> Self:
- ...
- return self
-
- """
-
- raise TypeError(f"{self} is not subscriptable")
-
-
-if hasattr(typing, "Never"): # 3.11+
- Never = typing.Never
-else:
- @_SpecialForm
- def Never(self, params):
- """The bottom type, a type that has no members.
-
- This can be used to define a function that should never be
- called, or a function that never returns::
-
- from pip._vendor.typing_extensions import Never
-
- def never_call_me(arg: Never) -> None:
- pass
-
- def int_or_str(arg: int | str) -> None:
- never_call_me(arg) # type checker error
- match arg:
- case int():
- print("It's an int")
- case str():
- print("It's a str")
- case _:
- never_call_me(arg) # ok, arg is of type Never
-
- """
-
- raise TypeError(f"{self} is not subscriptable")
-
-
-if hasattr(typing, 'Required'): # 3.11+
- Required = typing.Required
- NotRequired = typing.NotRequired
-elif sys.version_info[:2] >= (3, 9): # 3.9-3.10
- @_ExtensionsSpecialForm
- def Required(self, parameters):
- """A special typing construct to mark a key of a total=False TypedDict
- as required. For example:
-
- class Movie(TypedDict, total=False):
- title: Required[str]
- year: int
-
- m = Movie(
- title='The Matrix', # typechecker error if key is omitted
- year=1999,
- )
-
- There is no runtime checking that a required key is actually provided
- when instantiating a related TypedDict.
- """
- item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-
- @_ExtensionsSpecialForm
- def NotRequired(self, parameters):
- """A special typing construct to mark a key of a TypedDict as
- potentially missing. For example:
-
- class Movie(TypedDict):
- title: str
- year: NotRequired[int]
-
- m = Movie(
- title='The Matrix', # typechecker error if key is omitted
- year=1999,
- )
- """
- item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-
-else: # 3.8
- class _RequiredForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- item = typing._type_check(parameters,
- f'{self._name} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-
- Required = _RequiredForm(
- 'Required',
- doc="""A special typing construct to mark a key of a total=False TypedDict
- as required. For example:
-
- class Movie(TypedDict, total=False):
- title: Required[str]
- year: int
-
- m = Movie(
- title='The Matrix', # typechecker error if key is omitted
- year=1999,
- )
-
- There is no runtime checking that a required key is actually provided
- when instantiating a related TypedDict.
- """)
- NotRequired = _RequiredForm(
- 'NotRequired',
- doc="""A special typing construct to mark a key of a TypedDict as
- potentially missing. For example:
-
- class Movie(TypedDict):
- title: str
- year: NotRequired[int]
-
- m = Movie(
- title='The Matrix', # typechecker error if key is omitted
- year=1999,
- )
- """)
-
-
-if hasattr(typing, 'ReadOnly'):
- ReadOnly = typing.ReadOnly
-elif sys.version_info[:2] >= (3, 9): # 3.9-3.12
- @_ExtensionsSpecialForm
- def ReadOnly(self, parameters):
- """A special typing construct to mark an item of a TypedDict as read-only.
-
- For example:
-
- class Movie(TypedDict):
- title: ReadOnly[str]
- year: int
-
- def mutate_movie(m: Movie) -> None:
- m["year"] = 1992 # allowed
- m["title"] = "The Matrix" # typechecker error
-
- There is no runtime checking for this property.
- """
- item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-
-else: # 3.8
- class _ReadOnlyForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- item = typing._type_check(parameters,
- f'{self._name} accepts only a single type.')
- return typing._GenericAlias(self, (item,))
-
- ReadOnly = _ReadOnlyForm(
- 'ReadOnly',
- doc="""A special typing construct to mark a key of a TypedDict as read-only.
-
- For example:
-
- class Movie(TypedDict):
- title: ReadOnly[str]
- year: int
-
- def mutate_movie(m: Movie) -> None:
- m["year"] = 1992 # allowed
- m["title"] = "The Matrix" # typechecker error
-
- There is no runtime checking for this propery.
- """)
-
-
-_UNPACK_DOC = """\
-Type unpack operator.
-
-The type unpack operator takes the child types from some container type,
-such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
-example:
-
- # For some generic class `Foo`:
- Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str]
-
- Ts = TypeVarTuple('Ts')
- # Specifies that `Bar` is generic in an arbitrary number of types.
- # (Think of `Ts` as a tuple of an arbitrary number of individual
- # `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
- # `Generic[]`.)
- class Bar(Generic[Unpack[Ts]]): ...
- Bar[int] # Valid
- Bar[int, str] # Also valid
-
-From Python 3.11, this can also be done using the `*` operator:
-
- Foo[*tuple[int, str]]
- class Bar(Generic[*Ts]): ...
-
-The operator can also be used along with a `TypedDict` to annotate
-`**kwargs` in a function signature. For instance:
-
- class Movie(TypedDict):
- name: str
- year: int
-
- # This function expects two keyword arguments - *name* of type `str` and
- # *year* of type `int`.
- def foo(**kwargs: Unpack[Movie]): ...
-
-Note that there is only some runtime checking of this operator. Not
-everything the runtime allows may be accepted by static type checkers.
-
-For more information, see PEP 646 and PEP 692.
-"""
-
-
-if sys.version_info >= (3, 12): # PEP 692 changed the repr of Unpack[]
- Unpack = typing.Unpack
-
- def _is_unpack(obj):
- return get_origin(obj) is Unpack
-
-elif sys.version_info[:2] >= (3, 9): # 3.9+
- class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
- def __init__(self, getitem):
- super().__init__(getitem)
- self.__doc__ = _UNPACK_DOC
-
- class _UnpackAlias(typing._GenericAlias, _root=True):
- if sys.version_info < (3, 11):
- # needed for compatibility with Generic[Unpack[Ts]]
- __class__ = typing.TypeVar
-
- @property
- def __typing_unpacked_tuple_args__(self):
- assert self.__origin__ is Unpack
- assert len(self.__args__) == 1
- arg, = self.__args__
- if isinstance(arg, (typing._GenericAlias, _types.GenericAlias)):
- if arg.__origin__ is not tuple:
- raise TypeError("Unpack[...] must be used with a tuple type")
- return arg.__args__
- return None
-
- @property
- def __typing_is_unpacked_typevartuple__(self):
- assert self.__origin__ is Unpack
- assert len(self.__args__) == 1
- return isinstance(self.__args__[0], TypeVarTuple)
-
- def __getitem__(self, args):
- if self.__typing_is_unpacked_typevartuple__:
- return args
- return super().__getitem__(args)
-
- @_UnpackSpecialForm
- def Unpack(self, parameters):
- item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
- return _UnpackAlias(self, (item,))
-
- def _is_unpack(obj):
- return isinstance(obj, _UnpackAlias)
-
-else: # 3.8
- class _UnpackAlias(typing._GenericAlias, _root=True):
- __class__ = typing.TypeVar
-
- @property
- def __typing_unpacked_tuple_args__(self):
- assert self.__origin__ is Unpack
- assert len(self.__args__) == 1
- arg, = self.__args__
- if isinstance(arg, typing._GenericAlias):
- if arg.__origin__ is not tuple:
- raise TypeError("Unpack[...] must be used with a tuple type")
- return arg.__args__
- return None
-
- @property
- def __typing_is_unpacked_typevartuple__(self):
- assert self.__origin__ is Unpack
- assert len(self.__args__) == 1
- return isinstance(self.__args__[0], TypeVarTuple)
-
- def __getitem__(self, args):
- if self.__typing_is_unpacked_typevartuple__:
- return args
- return super().__getitem__(args)
-
- class _UnpackForm(_ExtensionsSpecialForm, _root=True):
- def __getitem__(self, parameters):
- item = typing._type_check(parameters,
- f'{self._name} accepts only a single type.')
- return _UnpackAlias(self, (item,))
-
- Unpack = _UnpackForm('Unpack', doc=_UNPACK_DOC)
-
- def _is_unpack(obj):
- return isinstance(obj, _UnpackAlias)
-
-
-def _unpack_args(*args):
- newargs = []
- for arg in args:
- subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
- if subargs is not None and (not (subargs and subargs[-1] is ...)):
- newargs.extend(subargs)
- else:
- newargs.append(arg)
- return newargs
-
-
-if _PEP_696_IMPLEMENTED:
- from typing import TypeVarTuple
-
-elif hasattr(typing, "TypeVarTuple"): # 3.11+
-
- # Add default parameter - PEP 696
- class TypeVarTuple(metaclass=_TypeVarLikeMeta):
- """Type variable tuple."""
-
- _backported_typevarlike = typing.TypeVarTuple
-
- def __new__(cls, name, *, default=NoDefault):
- tvt = typing.TypeVarTuple(name)
- _set_default(tvt, default)
- _set_module(tvt)
-
- def _typevartuple_prepare_subst(alias, args):
- params = alias.__parameters__
- typevartuple_index = params.index(tvt)
- for param in params[typevartuple_index + 1:]:
- if isinstance(param, TypeVarTuple):
- raise TypeError(
- f"More than one TypeVarTuple parameter in {alias}"
- )
-
- alen = len(args)
- plen = len(params)
- left = typevartuple_index
- right = plen - typevartuple_index - 1
- var_tuple_index = None
- fillarg = None
- for k, arg in enumerate(args):
- if not isinstance(arg, type):
- subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
- if subargs and len(subargs) == 2 and subargs[-1] is ...:
- if var_tuple_index is not None:
- raise TypeError(
- "More than one unpacked "
- "arbitrary-length tuple argument"
- )
- var_tuple_index = k
- fillarg = subargs[0]
- if var_tuple_index is not None:
- left = min(left, var_tuple_index)
- right = min(right, alen - var_tuple_index - 1)
- elif left + right > alen:
- raise TypeError(f"Too few arguments for {alias};"
- f" actual {alen}, expected at least {plen - 1}")
- if left == alen - right and tvt.has_default():
- replacement = _unpack_args(tvt.__default__)
- else:
- replacement = args[left: alen - right]
-
- return (
- *args[:left],
- *([fillarg] * (typevartuple_index - left)),
- replacement,
- *([fillarg] * (plen - right - left - typevartuple_index - 1)),
- *args[alen - right:],
- )
-
- tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst
- return tvt
-
- def __init_subclass__(self, *args, **kwds):
- raise TypeError("Cannot subclass special typing classes")
-
-else: # <=3.10
- class TypeVarTuple(_DefaultMixin):
- """Type variable tuple.
-
- Usage::
-
- Ts = TypeVarTuple('Ts')
-
- In the same way that a normal type variable is a stand-in for a single
- type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
- type such as ``Tuple[int, str]``.
-
- Type variable tuples can be used in ``Generic`` declarations.
- Consider the following example::
-
- class Array(Generic[*Ts]): ...
-
- The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
- where ``T1`` and ``T2`` are type variables. To use these type variables
- as type parameters of ``Array``, we must *unpack* the type variable tuple using
- the star operator: ``*Ts``. The signature of ``Array`` then behaves
- as if we had simply written ``class Array(Generic[T1, T2]): ...``.
- In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
- us to parameterise the class with an *arbitrary* number of type parameters.
-
- Type variable tuples can be used anywhere a normal ``TypeVar`` can.
- This includes class definitions, as shown above, as well as function
- signatures and variable annotations::
-
- class Array(Generic[*Ts]):
-
- def __init__(self, shape: Tuple[*Ts]):
- self._shape: Tuple[*Ts] = shape
-
- def get_shape(self) -> Tuple[*Ts]:
- return self._shape
-
- shape = (Height(480), Width(640))
- x: Array[Height, Width] = Array(shape)
- y = abs(x) # Inferred type is Array[Height, Width]
- z = x + x # ... is Array[Height, Width]
- x.get_shape() # ... is tuple[Height, Width]
-
- """
-
- # Trick Generic __parameters__.
- __class__ = typing.TypeVar
-
- def __iter__(self):
- yield self.__unpacked__
-
- def __init__(self, name, *, default=NoDefault):
- self.__name__ = name
- _DefaultMixin.__init__(self, default)
-
- # for pickling:
- def_mod = _caller()
- if def_mod != 'typing_extensions':
- self.__module__ = def_mod
-
- self.__unpacked__ = Unpack[self]
-
- def __repr__(self):
- return self.__name__
-
- def __hash__(self):
- return object.__hash__(self)
-
- def __eq__(self, other):
- return self is other
-
- def __reduce__(self):
- return self.__name__
-
- def __init_subclass__(self, *args, **kwds):
- if '_root' not in kwds:
- raise TypeError("Cannot subclass special typing classes")
-
-
-if hasattr(typing, "reveal_type"): # 3.11+
- reveal_type = typing.reveal_type
-else: # <=3.10
- def reveal_type(obj: T, /) -> T:
- """Reveal the inferred type of a variable.
-
- When a static type checker encounters a call to ``reveal_type()``,
- it will emit the inferred type of the argument::
-
- x: int = 1
- reveal_type(x)
-
- Running a static type checker (e.g., ``mypy``) on this example
- will produce output similar to 'Revealed type is "builtins.int"'.
-
- At runtime, the function prints the runtime type of the
- argument and returns it unchanged.
-
- """
- print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
- return obj
-
-
-if hasattr(typing, "_ASSERT_NEVER_REPR_MAX_LENGTH"): # 3.11+
- _ASSERT_NEVER_REPR_MAX_LENGTH = typing._ASSERT_NEVER_REPR_MAX_LENGTH
-else: # <=3.10
- _ASSERT_NEVER_REPR_MAX_LENGTH = 100
-
-
-if hasattr(typing, "assert_never"): # 3.11+
- assert_never = typing.assert_never
-else: # <=3.10
- def assert_never(arg: Never, /) -> Never:
- """Assert to the type checker that a line of code is unreachable.
-
- Example::
-
- def int_or_str(arg: int | str) -> None:
- match arg:
- case int():
- print("It's an int")
- case str():
- print("It's a str")
- case _:
- assert_never(arg)
-
- If a type checker finds that a call to assert_never() is
- reachable, it will emit an error.
-
- At runtime, this throws an exception when called.
-
- """
- value = repr(arg)
- if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH:
- value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + '...'
- raise AssertionError(f"Expected code to be unreachable, but got: {value}")
-
-
-if sys.version_info >= (3, 12): # 3.12+
- # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
- dataclass_transform = typing.dataclass_transform
-else: # <=3.11
- def dataclass_transform(
- *,
- eq_default: bool = True,
- order_default: bool = False,
- kw_only_default: bool = False,
- frozen_default: bool = False,
- field_specifiers: typing.Tuple[
- typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
- ...
- ] = (),
- **kwargs: typing.Any,
- ) -> typing.Callable[[T], T]:
- """Decorator that marks a function, class, or metaclass as providing
- dataclass-like behavior.
-
- Example:
-
- from pip._vendor.typing_extensions import dataclass_transform
-
- _T = TypeVar("_T")
-
- # Used on a decorator function
- @dataclass_transform()
- def create_model(cls: type[_T]) -> type[_T]:
- ...
- return cls
-
- @create_model
- class CustomerModel:
- id: int
- name: str
-
- # Used on a base class
- @dataclass_transform()
- class ModelBase: ...
-
- class CustomerModel(ModelBase):
- id: int
- name: str
-
- # Used on a metaclass
- @dataclass_transform()
- class ModelMeta(type): ...
-
- class ModelBase(metaclass=ModelMeta): ...
-
- class CustomerModel(ModelBase):
- id: int
- name: str
-
- Each of the ``CustomerModel`` classes defined in this example will now
- behave similarly to a dataclass created with the ``@dataclasses.dataclass``
- decorator. For example, the type checker will synthesize an ``__init__``
- method.
-
- The arguments to this decorator can be used to customize this behavior:
- - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
- True or False if it is omitted by the caller.
- - ``order_default`` indicates whether the ``order`` parameter is
- assumed to be True or False if it is omitted by the caller.
- - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
- assumed to be True or False if it is omitted by the caller.
- - ``frozen_default`` indicates whether the ``frozen`` parameter is
- assumed to be True or False if it is omitted by the caller.
- - ``field_specifiers`` specifies a static list of supported classes
- or functions that describe fields, similar to ``dataclasses.field()``.
-
- At runtime, this decorator records its arguments in the
- ``__dataclass_transform__`` attribute on the decorated object.
-
- See PEP 681 for details.
-
- """
- def decorator(cls_or_fn):
- cls_or_fn.__dataclass_transform__ = {
- "eq_default": eq_default,
- "order_default": order_default,
- "kw_only_default": kw_only_default,
- "frozen_default": frozen_default,
- "field_specifiers": field_specifiers,
- "kwargs": kwargs,
- }
- return cls_or_fn
- return decorator
-
-
-if hasattr(typing, "override"): # 3.12+
- override = typing.override
-else: # <=3.11
- _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
-
- def override(arg: _F, /) -> _F:
- """Indicate that a method is intended to override a method in a base class.
-
- Usage:
-
- class Base:
- def method(self) -> None:
- pass
-
- class Child(Base):
- @override
- def method(self) -> None:
- super().method()
-
- When this decorator is applied to a method, the type checker will
- validate that it overrides a method with the same name on a base class.
- This helps prevent bugs that may occur when a base class is changed
- without an equivalent change to a child class.
-
- There is no runtime checking of these properties. The decorator
- sets the ``__override__`` attribute to ``True`` on the decorated object
- to allow runtime introspection.
-
- See PEP 698 for details.
-
- """
- try:
- arg.__override__ = True
- except (AttributeError, TypeError):
- # Skip the attribute silently if it is not writable.
- # AttributeError happens if the object has __slots__ or a
- # read-only property, TypeError if it's a builtin class.
- pass
- return arg
-
-
-# Python 3.13.3+ contains a fix for the wrapped __new__
-if sys.version_info >= (3, 13, 3):
- deprecated = warnings.deprecated
-else:
- _T = typing.TypeVar("_T")
-
- class deprecated:
- """Indicate that a class, function or overload is deprecated.
-
- When this decorator is applied to an object, the type checker
- will generate a diagnostic on usage of the deprecated object.
-
- Usage:
-
- @deprecated("Use B instead")
- class A:
- pass
-
- @deprecated("Use g instead")
- def f():
- pass
-
- @overload
- @deprecated("int support is deprecated")
- def g(x: int) -> int: ...
- @overload
- def g(x: str) -> int: ...
-
- The warning specified by *category* will be emitted at runtime
- on use of deprecated objects. For functions, that happens on calls;
- for classes, on instantiation and on creation of subclasses.
- If the *category* is ``None``, no warning is emitted at runtime.
- The *stacklevel* determines where the
- warning is emitted. If it is ``1`` (the default), the warning
- is emitted at the direct caller of the deprecated object; if it
- is higher, it is emitted further up the stack.
- Static type checker behavior is not affected by the *category*
- and *stacklevel* arguments.
-
- The deprecation message passed to the decorator is saved in the
- ``__deprecated__`` attribute on the decorated object.
- If applied to an overload, the decorator
- must be after the ``@overload`` decorator for the attribute to
- exist on the overload as returned by ``get_overloads()``.
-
- See PEP 702 for details.
-
- """
- def __init__(
- self,
- message: str,
- /,
- *,
- category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
- stacklevel: int = 1,
- ) -> None:
- if not isinstance(message, str):
- raise TypeError(
- "Expected an object of type str for 'message', not "
- f"{type(message).__name__!r}"
- )
- self.message = message
- self.category = category
- self.stacklevel = stacklevel
-
- def __call__(self, arg: _T, /) -> _T:
- # Make sure the inner functions created below don't
- # retain a reference to self.
- msg = self.message
- category = self.category
- stacklevel = self.stacklevel
- if category is None:
- arg.__deprecated__ = msg
- return arg
- elif isinstance(arg, type):
- import functools
- from types import MethodType
-
- original_new = arg.__new__
-
- @functools.wraps(original_new)
- def __new__(cls, /, *args, **kwargs):
- if cls is arg:
- warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
- if original_new is not object.__new__:
- return original_new(cls, *args, **kwargs)
- # Mirrors a similar check in object.__new__.
- elif cls.__init__ is object.__init__ and (args or kwargs):
- raise TypeError(f"{cls.__name__}() takes no arguments")
- else:
- return original_new(cls)
-
- arg.__new__ = staticmethod(__new__)
-
- original_init_subclass = arg.__init_subclass__
- # We need slightly different behavior if __init_subclass__
- # is a bound method (likely if it was implemented in Python)
- if isinstance(original_init_subclass, MethodType):
- original_init_subclass = original_init_subclass.__func__
-
- @functools.wraps(original_init_subclass)
- def __init_subclass__(*args, **kwargs):
- warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
- return original_init_subclass(*args, **kwargs)
-
- arg.__init_subclass__ = classmethod(__init_subclass__)
- # Or otherwise, which likely means it's a builtin such as
- # object's implementation of __init_subclass__.
- else:
- @functools.wraps(original_init_subclass)
- def __init_subclass__(*args, **kwargs):
- warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
- return original_init_subclass(*args, **kwargs)
-
- arg.__init_subclass__ = __init_subclass__
-
- arg.__deprecated__ = __new__.__deprecated__ = msg
- __init_subclass__.__deprecated__ = msg
- return arg
- elif callable(arg):
- import asyncio.coroutines
- import functools
- import inspect
-
- @functools.wraps(arg)
- def wrapper(*args, **kwargs):
- warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
- return arg(*args, **kwargs)
-
- if asyncio.coroutines.iscoroutinefunction(arg):
- if sys.version_info >= (3, 12):
- wrapper = inspect.markcoroutinefunction(wrapper)
- else:
- wrapper._is_coroutine = asyncio.coroutines._is_coroutine
-
- arg.__deprecated__ = wrapper.__deprecated__ = msg
- return wrapper
- else:
- raise TypeError(
- "@deprecated decorator with non-None category must be applied to "
- f"a class or callable, not {arg!r}"
- )
-
-if sys.version_info < (3, 10):
- def _is_param_expr(arg):
- return arg is ... or isinstance(
- arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias)
- )
-else:
- def _is_param_expr(arg):
- return arg is ... or isinstance(
- arg,
- (
- tuple,
- list,
- ParamSpec,
- _ConcatenateGenericAlias,
- typing._ConcatenateGenericAlias,
- ),
- )
-
-
-# We have to do some monkey patching to deal with the dual nature of
-# Unpack/TypeVarTuple:
-# - We want Unpack to be a kind of TypeVar so it gets accepted in
-# Generic[Unpack[Ts]]
-# - We want it to *not* be treated as a TypeVar for the purposes of
-# counting generic parameters, so that when we subscript a generic,
-# the runtime doesn't try to substitute the Unpack with the subscripted type.
-if not hasattr(typing, "TypeVarTuple"):
- def _check_generic(cls, parameters, elen=_marker):
- """Check correct count for parameters of a generic cls (internal helper).
-
- This gives a nice error message in case of count mismatch.
- """
- # If substituting a single ParamSpec with multiple arguments
- # we do not check the count
- if (inspect.isclass(cls) and issubclass(cls, typing.Generic)
- and len(cls.__parameters__) == 1
- and isinstance(cls.__parameters__[0], ParamSpec)
- and parameters
- and not _is_param_expr(parameters[0])
- ):
- # Generic modifies parameters variable, but here we cannot do this
- return
-
- if not elen:
- raise TypeError(f"{cls} is not a generic class")
- if elen is _marker:
- if not hasattr(cls, "__parameters__") or not cls.__parameters__:
- raise TypeError(f"{cls} is not a generic class")
- elen = len(cls.__parameters__)
- alen = len(parameters)
- if alen != elen:
- expect_val = elen
- if hasattr(cls, "__parameters__"):
- parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
- num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
- if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
- return
-
- # deal with TypeVarLike defaults
- # required TypeVarLikes cannot appear after a defaulted one.
- if alen < elen:
- # since we validate TypeVarLike default in _collect_type_vars
- # or _collect_parameters we can safely check parameters[alen]
- if (
- getattr(parameters[alen], '__default__', NoDefault)
- is not NoDefault
- ):
- return
-
- num_default_tv = sum(getattr(p, '__default__', NoDefault)
- is not NoDefault for p in parameters)
-
- elen -= num_default_tv
-
- expect_val = f"at least {elen}"
-
- things = "arguments" if sys.version_info >= (3, 10) else "parameters"
- raise TypeError(f"Too {'many' if alen > elen else 'few'} {things}"
- f" for {cls}; actual {alen}, expected {expect_val}")
-else:
- # Python 3.11+
-
- def _check_generic(cls, parameters, elen):
- """Check correct count for parameters of a generic cls (internal helper).
-
- This gives a nice error message in case of count mismatch.
- """
- if not elen:
- raise TypeError(f"{cls} is not a generic class")
- alen = len(parameters)
- if alen != elen:
- expect_val = elen
- if hasattr(cls, "__parameters__"):
- parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
-
- # deal with TypeVarLike defaults
- # required TypeVarLikes cannot appear after a defaulted one.
- if alen < elen:
- # since we validate TypeVarLike default in _collect_type_vars
- # or _collect_parameters we can safely check parameters[alen]
- if (
- getattr(parameters[alen], '__default__', NoDefault)
- is not NoDefault
- ):
- return
-
- num_default_tv = sum(getattr(p, '__default__', NoDefault)
- is not NoDefault for p in parameters)
-
- elen -= num_default_tv
-
- expect_val = f"at least {elen}"
-
- raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments"
- f" for {cls}; actual {alen}, expected {expect_val}")
-
-if not _PEP_696_IMPLEMENTED:
- typing._check_generic = _check_generic
-
-
-def _has_generic_or_protocol_as_origin() -> bool:
- try:
- frame = sys._getframe(2)
- # - Catch AttributeError: not all Python implementations have sys._getframe()
- # - Catch ValueError: maybe we're called from an unexpected module
- # and the call stack isn't deep enough
- except (AttributeError, ValueError):
- return False # err on the side of leniency
- else:
- # If we somehow get invoked from outside typing.py,
- # also err on the side of leniency
- if frame.f_globals.get("__name__") != "typing":
- return False
- origin = frame.f_locals.get("origin")
- # Cannot use "in" because origin may be an object with a buggy __eq__ that
- # throws an error.
- return origin is typing.Generic or origin is Protocol or origin is typing.Protocol
-
-
-_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)}
-
-
-def _is_unpacked_typevartuple(x) -> bool:
- if get_origin(x) is not Unpack:
- return False
- args = get_args(x)
- return (
- bool(args)
- and len(args) == 1
- and type(args[0]) in _TYPEVARTUPLE_TYPES
- )
-
-
-# Python 3.11+ _collect_type_vars was renamed to _collect_parameters
-if hasattr(typing, '_collect_type_vars'):
- def _collect_type_vars(types, typevar_types=None):
- """Collect all type variable contained in types in order of
- first appearance (lexicographic order). For example::
-
- _collect_type_vars((T, List[S, T])) == (T, S)
- """
- if typevar_types is None:
- typevar_types = typing.TypeVar
- tvars = []
-
- # A required TypeVarLike cannot appear after a TypeVarLike with a default
- # if it was a direct call to `Generic[]` or `Protocol[]`
- enforce_default_ordering = _has_generic_or_protocol_as_origin()
- default_encountered = False
-
- # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
- type_var_tuple_encountered = False
-
- for t in types:
- if _is_unpacked_typevartuple(t):
- type_var_tuple_encountered = True
- elif (
- isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias)
- and t not in tvars
- ):
- if enforce_default_ordering:
- has_default = getattr(t, '__default__', NoDefault) is not NoDefault
- if has_default:
- if type_var_tuple_encountered:
- raise TypeError('Type parameter with a default'
- ' follows TypeVarTuple')
- default_encountered = True
- elif default_encountered:
- raise TypeError(f'Type parameter {t!r} without a default'
- ' follows type parameter with a default')
-
- tvars.append(t)
- if _should_collect_from_parameters(t):
- tvars.extend([t for t in t.__parameters__ if t not in tvars])
- elif isinstance(t, tuple):
- # Collect nested type_vars
- # tuple wrapped by _prepare_paramspec_params(cls, params)
- for x in t:
- for collected in _collect_type_vars([x]):
- if collected not in tvars:
- tvars.append(collected)
- return tuple(tvars)
-
- typing._collect_type_vars = _collect_type_vars
-else:
- def _collect_parameters(args):
- """Collect all type variables and parameter specifications in args
- in order of first appearance (lexicographic order).
-
- For example::
-
- assert _collect_parameters((T, Callable[P, T])) == (T, P)
- """
- parameters = []
-
- # A required TypeVarLike cannot appear after a TypeVarLike with default
- # if it was a direct call to `Generic[]` or `Protocol[]`
- enforce_default_ordering = _has_generic_or_protocol_as_origin()
- default_encountered = False
-
- # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
- type_var_tuple_encountered = False
-
- for t in args:
- if isinstance(t, type):
- # We don't want __parameters__ descriptor of a bare Python class.
- pass
- elif isinstance(t, tuple):
- # `t` might be a tuple, when `ParamSpec` is substituted with
- # `[T, int]`, or `[int, *Ts]`, etc.
- for x in t:
- for collected in _collect_parameters([x]):
- if collected not in parameters:
- parameters.append(collected)
- elif hasattr(t, '__typing_subst__'):
- if t not in parameters:
- if enforce_default_ordering:
- has_default = (
- getattr(t, '__default__', NoDefault) is not NoDefault
- )
-
- if type_var_tuple_encountered and has_default:
- raise TypeError('Type parameter with a default'
- ' follows TypeVarTuple')
-
- if has_default:
- default_encountered = True
- elif default_encountered:
- raise TypeError(f'Type parameter {t!r} without a default'
- ' follows type parameter with a default')
-
- parameters.append(t)
- else:
- if _is_unpacked_typevartuple(t):
- type_var_tuple_encountered = True
- for x in getattr(t, '__parameters__', ()):
- if x not in parameters:
- parameters.append(x)
-
- return tuple(parameters)
-
- if not _PEP_696_IMPLEMENTED:
- typing._collect_parameters = _collect_parameters
-
-# Backport typing.NamedTuple as it exists in Python 3.13.
-# In 3.11, the ability to define generic `NamedTuple`s was supported.
-# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
-# On 3.12, we added __orig_bases__ to call-based NamedTuples
-# On 3.13, we deprecated kwargs-based NamedTuples
-if sys.version_info >= (3, 13):
- NamedTuple = typing.NamedTuple
-else:
- def _make_nmtuple(name, types, module, defaults=()):
- fields = [n for n, t in types]
- annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
- for n, t in types}
- nm_tpl = collections.namedtuple(name, fields,
- defaults=defaults, module=module)
- nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
- # The `_field_types` attribute was removed in 3.9;
- # in earlier versions, it is the same as the `__annotations__` attribute
- if sys.version_info < (3, 9):
- nm_tpl._field_types = annotations
- return nm_tpl
-
- _prohibited_namedtuple_fields = typing._prohibited
- _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})
-
- class _NamedTupleMeta(type):
- def __new__(cls, typename, bases, ns):
- assert _NamedTuple in bases
- for base in bases:
- if base is not _NamedTuple and base is not typing.Generic:
- raise TypeError(
- 'can only inherit from a NamedTuple type and Generic')
- bases = tuple(tuple if base is _NamedTuple else base for base in bases)
- if "__annotations__" in ns:
- types = ns["__annotations__"]
- elif "__annotate__" in ns:
- # TODO: Use inspect.VALUE here, and make the annotations lazily evaluated
- types = ns["__annotate__"](1)
- else:
- types = {}
- default_names = []
- for field_name in types:
- if field_name in ns:
- default_names.append(field_name)
- elif default_names:
- raise TypeError(f"Non-default namedtuple field {field_name} "
- f"cannot follow default field"
- f"{'s' if len(default_names) > 1 else ''} "
- f"{', '.join(default_names)}")
- nm_tpl = _make_nmtuple(
- typename, types.items(),
- defaults=[ns[n] for n in default_names],
- module=ns['__module__']
- )
- nm_tpl.__bases__ = bases
- if typing.Generic in bases:
- if hasattr(typing, '_generic_class_getitem'): # 3.12+
- nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
- else:
- class_getitem = typing.Generic.__class_getitem__.__func__
- nm_tpl.__class_getitem__ = classmethod(class_getitem)
- # update from user namespace without overriding special namedtuple attributes
- for key, val in ns.items():
- if key in _prohibited_namedtuple_fields:
- raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
- elif key not in _special_namedtuple_fields:
- if key not in nm_tpl._fields:
- setattr(nm_tpl, key, ns[key])
- try:
- set_name = type(val).__set_name__
- except AttributeError:
- pass
- else:
- try:
- set_name(val, nm_tpl, key)
- except BaseException as e:
- msg = (
- f"Error calling __set_name__ on {type(val).__name__!r} "
- f"instance {key!r} in {typename!r}"
- )
- # BaseException.add_note() existed on py311,
- # but the __set_name__ machinery didn't start
- # using add_note() until py312.
- # Making sure exceptions are raised in the same way
- # as in "normal" classes seems most important here.
- if sys.version_info >= (3, 12):
- e.add_note(msg)
- raise
- else:
- raise RuntimeError(msg) from e
-
- if typing.Generic in bases:
- nm_tpl.__init_subclass__()
- return nm_tpl
-
- _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
-
- def _namedtuple_mro_entries(bases):
- assert NamedTuple in bases
- return (_NamedTuple,)
-
- @_ensure_subclassable(_namedtuple_mro_entries)
- def NamedTuple(typename, fields=_marker, /, **kwargs):
- """Typed version of namedtuple.
-
- Usage::
-
- class Employee(NamedTuple):
- name: str
- id: int
-
- This is equivalent to::
-
- Employee = collections.namedtuple('Employee', ['name', 'id'])
-
- The resulting class has an extra __annotations__ attribute, giving a
- dict that maps field names to types. (The field names are also in
- the _fields attribute, which is part of the namedtuple API.)
- An alternative equivalent functional syntax is also accepted::
-
- Employee = NamedTuple('Employee', [('name', str), ('id', int)])
- """
- if fields is _marker:
- if kwargs:
- deprecated_thing = "Creating NamedTuple classes using keyword arguments"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "Use the class-based or functional syntax instead."
- )
- else:
- deprecated_thing = "Failing to pass a value for the 'fields' parameter"
- example = f"`{typename} = NamedTuple({typename!r}, [])`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. "
- ) + example + "."
- elif fields is None:
- if kwargs:
- raise TypeError(
- "Cannot pass `None` as the 'fields' parameter "
- "and also specify fields using keyword arguments"
- )
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
- example = f"`{typename} = NamedTuple({typename!r}, [])`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. "
- ) + example + "."
- elif kwargs:
- raise TypeError("Either list of fields or keywords"
- " can be provided to NamedTuple, not both")
- if fields is _marker or fields is None:
- warnings.warn(
- deprecation_msg.format(name=deprecated_thing, remove="3.15"),
- DeprecationWarning,
- stacklevel=2,
- )
- fields = kwargs.items()
- nt = _make_nmtuple(typename, fields, module=_caller())
- nt.__orig_bases__ = (NamedTuple,)
- return nt
-
-
-if hasattr(collections.abc, "Buffer"):
- Buffer = collections.abc.Buffer
-else:
- class Buffer(abc.ABC): # noqa: B024
- """Base class for classes that implement the buffer protocol.
-
- The buffer protocol allows Python objects to expose a low-level
- memory buffer interface. Before Python 3.12, it is not possible
- to implement the buffer protocol in pure Python code, or even
- to check whether a class implements the buffer protocol. In
- Python 3.12 and higher, the ``__buffer__`` method allows access
- to the buffer protocol from Python code, and the
- ``collections.abc.Buffer`` ABC allows checking whether a class
- implements the buffer protocol.
-
- To indicate support for the buffer protocol in earlier versions,
- inherit from this ABC, either in a stub file or at runtime,
- or use ABC registration. This ABC provides no methods, because
- there is no Python-accessible methods shared by pre-3.12 buffer
- classes. It is useful primarily for static checks.
-
- """
-
- # As a courtesy, register the most common stdlib buffer classes.
- Buffer.register(memoryview)
- Buffer.register(bytearray)
- Buffer.register(bytes)
-
-
-# Backport of types.get_original_bases, available on 3.12+ in CPython
-if hasattr(_types, "get_original_bases"):
- get_original_bases = _types.get_original_bases
-else:
- def get_original_bases(cls, /):
- """Return the class's "original" bases prior to modification by `__mro_entries__`.
-
- Examples::
-
- from typing import TypeVar, Generic
- from pip._vendor.typing_extensions import NamedTuple, TypedDict
-
- T = TypeVar("T")
- class Foo(Generic[T]): ...
- class Bar(Foo[int], float): ...
- class Baz(list[str]): ...
- Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
- Spam = TypedDict("Spam", {"a": int, "b": str})
-
- assert get_original_bases(Bar) == (Foo[int], float)
- assert get_original_bases(Baz) == (list[str],)
- assert get_original_bases(Eggs) == (NamedTuple,)
- assert get_original_bases(Spam) == (TypedDict,)
- assert get_original_bases(int) == (object,)
- """
- try:
- return cls.__dict__.get("__orig_bases__", cls.__bases__)
- except AttributeError:
- raise TypeError(
- f'Expected an instance of type, not {type(cls).__name__!r}'
- ) from None
-
-
-# NewType is a class on Python 3.10+, making it pickleable
-# The error message for subclassing instances of NewType was improved on 3.11+
-if sys.version_info >= (3, 11):
- NewType = typing.NewType
-else:
- class NewType:
- """NewType creates simple unique types with almost zero
- runtime overhead. NewType(name, tp) is considered a subtype of tp
- by static type checkers. At runtime, NewType(name, tp) returns
- a dummy callable that simply returns its argument. Usage::
- UserId = NewType('UserId', int)
- def name_by_id(user_id: UserId) -> str:
- ...
- UserId('user') # Fails type check
- name_by_id(42) # Fails type check
- name_by_id(UserId(42)) # OK
- num = UserId(5) + 1 # type: int
- """
-
- def __call__(self, obj, /):
- return obj
-
- def __init__(self, name, tp):
- self.__qualname__ = name
- if '.' in name:
- name = name.rpartition('.')[-1]
- self.__name__ = name
- self.__supertype__ = tp
- def_mod = _caller()
- if def_mod != 'typing_extensions':
- self.__module__ = def_mod
-
- def __mro_entries__(self, bases):
- # We defined __mro_entries__ to get a better error message
- # if a user attempts to subclass a NewType instance. bpo-46170
- supercls_name = self.__name__
-
- class Dummy:
- def __init_subclass__(cls):
- subcls_name = cls.__name__
- raise TypeError(
- f"Cannot subclass an instance of NewType. "
- f"Perhaps you were looking for: "
- f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`"
- )
-
- return (Dummy,)
-
- def __repr__(self):
- return f'{self.__module__}.{self.__qualname__}'
-
- def __reduce__(self):
- return self.__qualname__
-
- if sys.version_info >= (3, 10):
- # PEP 604 methods
- # It doesn't make sense to have these methods on Python <3.10
-
- def __or__(self, other):
- return typing.Union[self, other]
-
- def __ror__(self, other):
- return typing.Union[other, self]
-
-
-if sys.version_info >= (3, 14):
- TypeAliasType = typing.TypeAliasType
-# 3.8-3.13
-else:
- if sys.version_info >= (3, 12):
- # 3.12-3.14
- def _is_unionable(obj):
- """Corresponds to is_unionable() in unionobject.c in CPython."""
- return obj is None or isinstance(obj, (
- type,
- _types.GenericAlias,
- _types.UnionType,
- typing.TypeAliasType,
- TypeAliasType,
- ))
- else:
- # 3.8-3.11
- def _is_unionable(obj):
- """Corresponds to is_unionable() in unionobject.c in CPython."""
- return obj is None or isinstance(obj, (
- type,
- _types.GenericAlias,
- _types.UnionType,
- TypeAliasType,
- ))
-
- if sys.version_info < (3, 10):
- # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
- # so that we emulate the behaviour of `types.GenericAlias`
- # on the latest versions of CPython
- _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({
- "__class__",
- "__bases__",
- "__origin__",
- "__args__",
- "__unpacked__",
- "__parameters__",
- "__typing_unpacked_tuple_args__",
- "__mro_entries__",
- "__reduce_ex__",
- "__reduce__",
- "__copy__",
- "__deepcopy__",
- })
-
- class _TypeAliasGenericAlias(typing._GenericAlias, _root=True):
- def __getattr__(self, attr):
- if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS:
- return object.__getattr__(self, attr)
- return getattr(self.__origin__, attr)
-
- if sys.version_info < (3, 9):
- def __getitem__(self, item):
- result = super().__getitem__(item)
- result.__class__ = type(self)
- return result
-
- class TypeAliasType:
- """Create named, parameterized type aliases.
-
- This provides a backport of the new `type` statement in Python 3.12:
-
- type ListOrSet[T] = list[T] | set[T]
-
- is equivalent to:
-
- T = TypeVar("T")
- ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))
-
- The name ListOrSet can then be used as an alias for the type it refers to.
-
- The type_params argument should contain all the type parameters used
- in the value of the type alias. If the alias is not generic, this
- argument is omitted.
-
- Static type checkers should only support type aliases declared using
- TypeAliasType that follow these rules:
-
- - The first argument (the name) must be a string literal.
- - The TypeAliasType instance must be immediately assigned to a variable
- of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
- as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').
-
- """
-
- def __init__(self, name: str, value, *, type_params=()):
- if not isinstance(name, str):
- raise TypeError("TypeAliasType name must be a string")
- if not isinstance(type_params, tuple):
- raise TypeError("type_params must be a tuple")
- self.__value__ = value
- self.__type_params__ = type_params
-
- default_value_encountered = False
- parameters = []
- for type_param in type_params:
- if (
- not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
- # 3.8-3.11
- # Unpack Backport passes isinstance(type_param, TypeVar)
- or _is_unpack(type_param)
- ):
- raise TypeError(f"Expected a type param, got {type_param!r}")
- has_default = (
- getattr(type_param, '__default__', NoDefault) is not NoDefault
- )
- if default_value_encountered and not has_default:
- raise TypeError(f"non-default type parameter '{type_param!r}'"
- " follows default type parameter")
- if has_default:
- default_value_encountered = True
- if isinstance(type_param, TypeVarTuple):
- parameters.extend(type_param)
- else:
- parameters.append(type_param)
- self.__parameters__ = tuple(parameters)
- def_mod = _caller()
- if def_mod != 'typing_extensions':
- self.__module__ = def_mod
- # Setting this attribute closes the TypeAliasType from further modification
- self.__name__ = name
-
- def __setattr__(self, name: str, value: object, /) -> None:
- if hasattr(self, "__name__"):
- self._raise_attribute_error(name)
- super().__setattr__(name, value)
-
- def __delattr__(self, name: str, /) -> Never:
- self._raise_attribute_error(name)
-
- def _raise_attribute_error(self, name: str) -> Never:
- # Match the Python 3.12 error messages exactly
- if name == "__name__":
- raise AttributeError("readonly attribute")
- elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
- raise AttributeError(
- f"attribute '{name}' of 'typing.TypeAliasType' objects "
- "is not writable"
- )
- else:
- raise AttributeError(
- f"'typing.TypeAliasType' object has no attribute '{name}'"
- )
-
- def __repr__(self) -> str:
- return self.__name__
-
- if sys.version_info < (3, 11):
- def _check_single_param(self, param, recursion=0):
- # Allow [], [int], [int, str], [int, ...], [int, T]
- if param is ...:
- return ...
- if param is None:
- return None
- # Note in <= 3.9 _ConcatenateGenericAlias inherits from list
- if isinstance(param, list) and recursion == 0:
- return [self._check_single_param(arg, recursion+1)
- for arg in param]
- return typing._type_check(
- param, f'Subscripting {self.__name__} requires a type.'
- )
-
- def _check_parameters(self, parameters):
- if sys.version_info < (3, 11):
- return tuple(
- self._check_single_param(item)
- for item in parameters
- )
- return tuple(typing._type_check(
- item, f'Subscripting {self.__name__} requires a type.'
- )
- for item in parameters
- )
-
- def __getitem__(self, parameters):
- if not self.__type_params__:
- raise TypeError("Only generic type aliases are subscriptable")
- if not isinstance(parameters, tuple):
- parameters = (parameters,)
- # Using 3.9 here will create problems with Concatenate
- if sys.version_info >= (3, 10):
- return _types.GenericAlias(self, parameters)
- type_vars = _collect_type_vars(parameters)
- parameters = self._check_parameters(parameters)
- alias = _TypeAliasGenericAlias(self, parameters)
- # alias.__parameters__ is not complete if Concatenate is present
- # as it is converted to a list from which no parameters are extracted.
- if alias.__parameters__ != type_vars:
- alias.__parameters__ = type_vars
- return alias
-
- def __reduce__(self):
- return self.__name__
-
- def __init_subclass__(cls, *args, **kwargs):
- raise TypeError(
- "type 'typing_extensions.TypeAliasType' is not an acceptable base type"
- )
-
- # The presence of this method convinces typing._type_check
- # that TypeAliasTypes are types.
- def __call__(self):
- raise TypeError("Type alias is not callable")
-
- if sys.version_info >= (3, 10):
- def __or__(self, right):
- # For forward compatibility with 3.12, reject Unions
- # that are not accepted by the built-in Union.
- if not _is_unionable(right):
- return NotImplemented
- return typing.Union[self, right]
-
- def __ror__(self, left):
- if not _is_unionable(left):
- return NotImplemented
- return typing.Union[left, self]
-
-
-if hasattr(typing, "is_protocol"):
- is_protocol = typing.is_protocol
- get_protocol_members = typing.get_protocol_members
-else:
- def is_protocol(tp: type, /) -> bool:
- """Return True if the given type is a Protocol.
-
- Example::
-
- >>> from typing_extensions import Protocol, is_protocol
- >>> class P(Protocol):
- ... def a(self) -> str: ...
- ... b: int
- >>> is_protocol(P)
- True
- >>> is_protocol(int)
- False
- """
- return (
- isinstance(tp, type)
- and getattr(tp, '_is_protocol', False)
- and tp is not Protocol
- and tp is not typing.Protocol
- )
-
- def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
- """Return the set of members defined in a Protocol.
-
- Example::
-
- >>> from typing_extensions import Protocol, get_protocol_members
- >>> class P(Protocol):
- ... def a(self) -> str: ...
- ... b: int
- >>> get_protocol_members(P)
- frozenset({'a', 'b'})
-
- Raise a TypeError for arguments that are not Protocols.
- """
- if not is_protocol(tp):
- raise TypeError(f'{tp!r} is not a Protocol')
- if hasattr(tp, '__protocol_attrs__'):
- return frozenset(tp.__protocol_attrs__)
- return frozenset(_get_protocol_attrs(tp))
-
-
-if hasattr(typing, "Doc"):
- Doc = typing.Doc
-else:
- class Doc:
- """Define the documentation of a type annotation using ``Annotated``, to be
- used in class attributes, function and method parameters, return values,
- and variables.
-
- The value should be a positional-only string literal to allow static tools
- like editors and documentation generators to use it.
-
- This complements docstrings.
-
- The string value passed is available in the attribute ``documentation``.
-
- Example::
-
- >>> from typing_extensions import Annotated, Doc
- >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
- """
- def __init__(self, documentation: str, /) -> None:
- self.documentation = documentation
-
- def __repr__(self) -> str:
- return f"Doc({self.documentation!r})"
-
- def __hash__(self) -> int:
- return hash(self.documentation)
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, Doc):
- return NotImplemented
- return self.documentation == other.documentation
-
-
-_CapsuleType = getattr(_types, "CapsuleType", None)
-
-if _CapsuleType is None:
- try:
- import _socket
- except ImportError:
- pass
- else:
- _CAPI = getattr(_socket, "CAPI", None)
- if _CAPI is not None:
- _CapsuleType = type(_CAPI)
-
-if _CapsuleType is not None:
- CapsuleType = _CapsuleType
- __all__.append("CapsuleType")
-
-
-# Using this convoluted approach so that this keeps working
-# whether we end up using PEP 649 as written, PEP 749, or
-# some other variation: in any case, inspect.get_annotations
-# will continue to exist and will gain a `format` parameter.
-_PEP_649_OR_749_IMPLEMENTED = (
- hasattr(inspect, 'get_annotations')
- and inspect.get_annotations.__kwdefaults__ is not None
- and "format" in inspect.get_annotations.__kwdefaults__
-)
-
-
-class Format(enum.IntEnum):
- VALUE = 1
- FORWARDREF = 2
- STRING = 3
-
-
-if _PEP_649_OR_749_IMPLEMENTED:
- get_annotations = inspect.get_annotations
-else:
- def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
- format=Format.VALUE):
- """Compute the annotations dict for an object.
-
- obj may be a callable, class, or module.
- Passing in an object of any other type raises TypeError.
-
- Returns a dict. get_annotations() returns a new dict every time
- it's called; calling it twice on the same object will return two
- different but equivalent dicts.
-
- This is a backport of `inspect.get_annotations`, which has been
- in the standard library since Python 3.10. See the standard library
- documentation for more:
-
- https://docs.python.org/3/library/inspect.html#inspect.get_annotations
-
- This backport adds the *format* argument introduced by PEP 649. The
- three formats supported are:
- * VALUE: the annotations are returned as-is. This is the default and
- it is compatible with the behavior on previous Python versions.
- * FORWARDREF: return annotations as-is if possible, but replace any
- undefined names with ForwardRef objects. The implementation proposed by
- PEP 649 relies on language changes that cannot be backported; the
- typing-extensions implementation simply returns the same result as VALUE.
- * STRING: return annotations as strings, in a format close to the original
- source. Again, this behavior cannot be replicated directly in a backport.
- As an approximation, typing-extensions retrieves the annotations under
- VALUE semantics and then stringifies them.
-
- The purpose of this backport is to allow users who would like to use
- FORWARDREF or STRING semantics once PEP 649 is implemented, but who also
- want to support earlier Python versions, to simply write:
-
- typing_extensions.get_annotations(obj, format=Format.FORWARDREF)
-
- """
- format = Format(format)
-
- if eval_str and format is not Format.VALUE:
- raise ValueError("eval_str=True is only supported with format=Format.VALUE")
-
- if isinstance(obj, type):
- # class
- obj_dict = getattr(obj, '__dict__', None)
- if obj_dict and hasattr(obj_dict, 'get'):
- ann = obj_dict.get('__annotations__', None)
- if isinstance(ann, _types.GetSetDescriptorType):
- ann = None
- else:
- ann = None
-
- obj_globals = None
- module_name = getattr(obj, '__module__', None)
- if module_name:
- module = sys.modules.get(module_name, None)
- if module:
- obj_globals = getattr(module, '__dict__', None)
- obj_locals = dict(vars(obj))
- unwrap = obj
- elif isinstance(obj, _types.ModuleType):
- # module
- ann = getattr(obj, '__annotations__', None)
- obj_globals = obj.__dict__
- obj_locals = None
- unwrap = None
- elif callable(obj):
- # this includes types.Function, types.BuiltinFunctionType,
- # types.BuiltinMethodType, functools.partial, functools.singledispatch,
- # "class funclike" from Lib/test/test_inspect... on and on it goes.
- ann = getattr(obj, '__annotations__', None)
- obj_globals = getattr(obj, '__globals__', None)
- obj_locals = None
- unwrap = obj
- elif hasattr(obj, '__annotations__'):
- ann = obj.__annotations__
- obj_globals = obj_locals = unwrap = None
- else:
- raise TypeError(f"{obj!r} is not a module, class, or callable.")
-
- if ann is None:
- return {}
-
- if not isinstance(ann, dict):
- raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
-
- if not ann:
- return {}
-
- if not eval_str:
- if format is Format.STRING:
- return {
- key: value if isinstance(value, str) else typing._type_repr(value)
- for key, value in ann.items()
- }
- return dict(ann)
-
- if unwrap is not None:
- while True:
- if hasattr(unwrap, '__wrapped__'):
- unwrap = unwrap.__wrapped__
- continue
- if isinstance(unwrap, functools.partial):
- unwrap = unwrap.func
- continue
- break
- if hasattr(unwrap, "__globals__"):
- obj_globals = unwrap.__globals__
-
- if globals is None:
- globals = obj_globals
- if locals is None:
- locals = obj_locals or {}
-
- # "Inject" type parameters into the local namespace
- # (unless they are shadowed by assignments *in* the local namespace),
- # as a way of emulating annotation scopes when calling `eval()`
- if type_params := getattr(obj, "__type_params__", ()):
- locals = {param.__name__: param for param in type_params} | locals
-
- return_value = {key:
- value if not isinstance(value, str) else eval(value, globals, locals)
- for key, value in ann.items() }
- return return_value
-
-
-if hasattr(typing, "evaluate_forward_ref"):
- evaluate_forward_ref = typing.evaluate_forward_ref
-else:
- # Implements annotationlib.ForwardRef.evaluate
- def _eval_with_owner(
- forward_ref, *, owner=None, globals=None, locals=None, type_params=None
- ):
- if forward_ref.__forward_evaluated__:
- return forward_ref.__forward_value__
- if getattr(forward_ref, "__cell__", None) is not None:
- try:
- value = forward_ref.__cell__.cell_contents
- except ValueError:
- pass
- else:
- forward_ref.__forward_evaluated__ = True
- forward_ref.__forward_value__ = value
- return value
- if owner is None:
- owner = getattr(forward_ref, "__owner__", None)
-
- if (
- globals is None
- and getattr(forward_ref, "__forward_module__", None) is not None
- ):
- globals = getattr(
- sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None
- )
- if globals is None:
- globals = getattr(forward_ref, "__globals__", None)
- if globals is None:
- if isinstance(owner, type):
- module_name = getattr(owner, "__module__", None)
- if module_name:
- module = sys.modules.get(module_name, None)
- if module:
- globals = getattr(module, "__dict__", None)
- elif isinstance(owner, _types.ModuleType):
- globals = getattr(owner, "__dict__", None)
- elif callable(owner):
- globals = getattr(owner, "__globals__", None)
-
- # If we pass None to eval() below, the globals of this module are used.
- if globals is None:
- globals = {}
-
- if locals is None:
- locals = {}
- if isinstance(owner, type):
- locals.update(vars(owner))
-
- if type_params is None and owner is not None:
- # "Inject" type parameters into the local namespace
- # (unless they are shadowed by assignments *in* the local namespace),
- # as a way of emulating annotation scopes when calling `eval()`
- type_params = getattr(owner, "__type_params__", None)
-
- # type parameters require some special handling,
- # as they exist in their own scope
- # but `eval()` does not have a dedicated parameter for that scope.
- # For classes, names in type parameter scopes should override
- # names in the global scope (which here are called `localns`!),
- # but should in turn be overridden by names in the class scope
- # (which here are called `globalns`!)
- if type_params is not None:
- globals = dict(globals)
- locals = dict(locals)
- for param in type_params:
- param_name = param.__name__
- if (
- _FORWARD_REF_HAS_CLASS and not forward_ref.__forward_is_class__
- ) or param_name not in globals:
- globals[param_name] = param
- locals.pop(param_name, None)
-
- arg = forward_ref.__forward_arg__
- if arg.isidentifier() and not keyword.iskeyword(arg):
- if arg in locals:
- value = locals[arg]
- elif arg in globals:
- value = globals[arg]
- elif hasattr(builtins, arg):
- return getattr(builtins, arg)
- else:
- raise NameError(arg)
- else:
- code = forward_ref.__forward_code__
- value = eval(code, globals, locals)
- forward_ref.__forward_evaluated__ = True
- forward_ref.__forward_value__ = value
- return value
-
- def _lax_type_check(
- value, msg, is_argument=True, *, module=None, allow_special_forms=False
- ):
- """
- A lax Python 3.11+ like version of typing._type_check
- """
- if hasattr(typing, "_type_convert"):
- if (
- sys.version_info >= (3, 10, 3)
- or (3, 9, 10) < sys.version_info[:3] < (3, 10)
- ):
- # allow_special_forms introduced later cpython/#30926 (bpo-46539)
- type_ = typing._type_convert(
- value,
- module=module,
- allow_special_forms=allow_special_forms,
- )
- # module was added with bpo-41249 before is_class (bpo-46539)
- elif "__forward_module__" in typing.ForwardRef.__slots__:
- type_ = typing._type_convert(value, module=module)
- else:
- type_ = typing._type_convert(value)
- else:
- if value is None:
- return type(None)
- if isinstance(value, str):
- return ForwardRef(value)
- type_ = value
- invalid_generic_forms = (Generic, Protocol)
- if not allow_special_forms:
- invalid_generic_forms += (ClassVar,)
- if is_argument:
- invalid_generic_forms += (Final,)
- if (
- isinstance(type_, typing._GenericAlias)
- and get_origin(type_) in invalid_generic_forms
- ):
- raise TypeError(f"{type_} is not valid as type argument") from None
- if type_ in (Any, LiteralString, NoReturn, Never, Self, TypeAlias):
- return type_
- if allow_special_forms and type_ in (ClassVar, Final):
- return type_
- if (
- isinstance(type_, (_SpecialForm, typing._SpecialForm))
- or type_ in (Generic, Protocol)
- ):
- raise TypeError(f"Plain {type_} is not valid as type argument") from None
- if type(type_) is tuple: # lax version with tuple instead of callable
- raise TypeError(f"{msg} Got {type_!r:.100}.")
- return type_
-
- def evaluate_forward_ref(
- forward_ref,
- *,
- owner=None,
- globals=None,
- locals=None,
- type_params=None,
- format=Format.VALUE,
- _recursive_guard=frozenset(),
- ):
- """Evaluate a forward reference as a type hint.
-
- This is similar to calling the ForwardRef.evaluate() method,
- but unlike that method, evaluate_forward_ref() also:
-
- * Recursively evaluates forward references nested within the type hint.
- * Rejects certain objects that are not valid type hints.
- * Replaces type hints that evaluate to None with types.NoneType.
- * Supports the *FORWARDREF* and *STRING* formats.
-
- *forward_ref* must be an instance of ForwardRef. *owner*, if given,
- should be the object that holds the annotations that the forward reference
- derived from, such as a module, class object, or function. It is used to
- infer the namespaces to use for looking up names. *globals* and *locals*
- can also be explicitly given to provide the global and local namespaces.
- *type_params* is a tuple of type parameters that are in scope when
- evaluating the forward reference. This parameter must be provided (though
- it may be an empty tuple) if *owner* is not given and the forward reference
- does not already have an owner set. *format* specifies the format of the
- annotation and is a member of the annotationlib.Format enum.
-
- """
- if format == Format.STRING:
- return forward_ref.__forward_arg__
- if forward_ref.__forward_arg__ in _recursive_guard:
- return forward_ref
-
- # Evaluate the forward reference
- try:
- value = _eval_with_owner(
- forward_ref,
- owner=owner,
- globals=globals,
- locals=locals,
- type_params=type_params,
- )
- except NameError:
- if format == Format.FORWARDREF:
- return forward_ref
- else:
- raise
-
- msg = "Forward references must evaluate to types."
- if not _FORWARD_REF_HAS_CLASS:
- allow_special_forms = not forward_ref.__forward_is_argument__
- else:
- allow_special_forms = forward_ref.__forward_is_class__
- type_ = _lax_type_check(
- value,
- msg,
- is_argument=forward_ref.__forward_is_argument__,
- allow_special_forms=allow_special_forms,
- )
-
- # Recursively evaluate the type
- if isinstance(type_, ForwardRef):
- if getattr(type_, "__forward_module__", True) is not None:
- globals = None
- return evaluate_forward_ref(
- type_,
- globals=globals,
- locals=locals,
- type_params=type_params, owner=owner,
- _recursive_guard=_recursive_guard, format=format
- )
- if sys.version_info < (3, 12, 5) and type_params:
- # Make use of type_params
- locals = dict(locals) if locals else {}
- for tvar in type_params:
- if tvar.__name__ not in locals: # lets not overwrite something present
- locals[tvar.__name__] = tvar
- if sys.version_info < (3, 9):
- return typing._eval_type(
- type_,
- globals,
- locals,
- )
- if sys.version_info < (3, 12, 5):
- return typing._eval_type(
- type_,
- globals,
- locals,
- recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
- )
- if sys.version_info < (3, 14):
- return typing._eval_type(
- type_,
- globals,
- locals,
- type_params,
- recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
- )
- return typing._eval_type(
- type_,
- globals,
- locals,
- type_params,
- recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
- format=format,
- owner=owner,
- )
-
-
-# Aliases for items that have always been in typing.
-# Explicitly assign these (rather than using `from typing import *` at the top),
-# so that we get a CI error if one of these is deleted from typing.py
-# in a future version of Python
-AbstractSet = typing.AbstractSet
-AnyStr = typing.AnyStr
-BinaryIO = typing.BinaryIO
-Callable = typing.Callable
-Collection = typing.Collection
-Container = typing.Container
-Dict = typing.Dict
-ForwardRef = typing.ForwardRef
-FrozenSet = typing.FrozenSet
-Generic = typing.Generic
-Hashable = typing.Hashable
-IO = typing.IO
-ItemsView = typing.ItemsView
-Iterable = typing.Iterable
-Iterator = typing.Iterator
-KeysView = typing.KeysView
-List = typing.List
-Mapping = typing.Mapping
-MappingView = typing.MappingView
-Match = typing.Match
-MutableMapping = typing.MutableMapping
-MutableSequence = typing.MutableSequence
-MutableSet = typing.MutableSet
-Optional = typing.Optional
-Pattern = typing.Pattern
-Reversible = typing.Reversible
-Sequence = typing.Sequence
-Set = typing.Set
-Sized = typing.Sized
-TextIO = typing.TextIO
-Tuple = typing.Tuple
-Union = typing.Union
-ValuesView = typing.ValuesView
-cast = typing.cast
-no_type_check = typing.no_type_check
-no_type_check_decorator = typing.no_type_check_decorator
diff --git a/contrib/python/pip/pip/_vendor/vendor.txt b/contrib/python/pip/pip/_vendor/vendor.txt
index b6597dc0022..89908dc959b 100644
--- a/contrib/python/pip/pip/_vendor/vendor.txt
+++ b/contrib/python/pip/pip/_vendor/vendor.txt
@@ -1,18 +1,17 @@
-CacheControl==0.14.2
-distlib==0.3.9
+CacheControl==0.14.3
+distlib==0.4.0
distro==1.9.0
-msgpack==1.1.0
+msgpack==1.1.1
packaging==25.0
-platformdirs==4.3.7
+platformdirs==4.3.8
pyproject-hooks==1.2.0
-requests==2.32.3
- certifi==2025.1.31
+requests==2.32.4
+ certifi==2025.7.14
idna==3.10
urllib3==1.26.20
-rich==14.0.0
- pygments==2.19.1
- typing_extensions==4.13.2
-resolvelib==1.1.0
+rich==14.1.0
+ pygments==2.19.2
+resolvelib==1.2.0
setuptools==70.3.0
tomli==2.2.1
tomli-w==1.2.0
diff --git a/contrib/python/pip/ya.make b/contrib/python/pip/ya.make
index 0a5893b069f..3d24b6fe9ff 100644
--- a/contrib/python/pip/ya.make
+++ b/contrib/python/pip/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(25.1.1)
+VERSION(25.2)
LICENSE(MIT)
@@ -194,17 +194,9 @@ PY_SRCS(
pip/_vendor/dependency_groups/_toml_compat.py
pip/_vendor/distlib/__init__.py
pip/_vendor/distlib/compat.py
- pip/_vendor/distlib/database.py
- pip/_vendor/distlib/index.py
- pip/_vendor/distlib/locators.py
- pip/_vendor/distlib/manifest.py
- pip/_vendor/distlib/markers.py
- pip/_vendor/distlib/metadata.py
pip/_vendor/distlib/resources.py
pip/_vendor/distlib/scripts.py
pip/_vendor/distlib/util.py
- pip/_vendor/distlib/version.py
- pip/_vendor/distlib/wheel.py
pip/_vendor/distro/__init__.py
pip/_vendor/distro/__main__.py
pip/_vendor/distro/distro.py
@@ -388,7 +380,6 @@ PY_SRCS(
pip/_vendor/truststore/_openssl.py
pip/_vendor/truststore/_ssl_constants.py
pip/_vendor/truststore/_windows.py
- pip/_vendor/typing_extensions.py
pip/_vendor/urllib3/__init__.py
pip/_vendor/urllib3/_collections.py
pip/_vendor/urllib3/_version.py