Compare commits

..

118 Commits

Author SHA1 Message Date
Pradyun Gedam e3381891ce
Merge 97a447ba84 into a15dd75d98 2023-12-10 09:03:11 +00:00
Tzu-ping Chung a15dd75d98
Merge pull request #12417 from xqm32/fix-outdated-pip-install 2023-11-28 16:08:29 +09:00
Tzu-ping Chung d8ab6dc6c1 Clarify news fragment 2023-11-28 15:06:25 +08:00
Qiming Xu fe10d368f6
Add end line 2023-11-28 14:25:56 +08:00
Qiming Xu 28250baffb
Fix line wrap length and add news entry 2023-11-28 14:17:51 +08:00
Qiming Xu 88ac529219
Fix outdated pip install argument description 2023-11-28 13:15:31 +08:00
Damian Shaw 2a0acb595c
Update and provide fixes for mypy pre-commit (#12389)
* Update mypy to 1.6.1

* Fix mypy "Source file found twice under different module names" error

* Ignore type of intialized abstract class in tests

* Use more specific type ignore method-assign

* Type ignore for message.get_all

* Remove unused type ignore

* Add SizedBuffer type for xmlrpc.client.Transport subclass

* Add Self type for RequestHandlerClass in test

* Add type ignore for shutil.rmtree onexc handler

* Quote SizedBuffer

* Add news entry

* Remove no longer correct comment

* Update self import

* Also ignore type onerror=handler

* Update news entry

* Update news entry
2023-11-07 09:39:01 +00:00
Damian Shaw 68529081c2
Enforce f-strings via Ruff (#12393) 2023-11-07 09:14:56 +00:00
Damian Shaw 9685f64fe8
Update ruff and config (#12390) 2023-11-06 09:30:05 +00:00
Dale fd77ebfc74
Rework the functionality of PIP_CONFIG_FILE (#11850) 2023-10-27 14:59:56 +02:00
efflamlemaillet 6dbd9c68f0
Fix hg: "parse error at 0: not a prefix:" (#12373)
Use two hypen argument `--rev=` instead of `-r=`

Co-authored-by: Efflam Lemaillet <elemaillet@logilab.fr>
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
2023-10-27 11:08:17 +02:00
Stéphane Bidoul 7aaca9f2c4
Merge pull request #12370 from sbidoul/release/23.3.1
Release/23.3.1
2023-10-21 13:05:39 +02:00
Stéphane Bidoul 576dbd813c Bump for development 2023-10-21 12:57:41 +02:00
Stéphane Bidoul 5364f26f96 Bump for release 2023-10-21 12:57:31 +02:00
Itamar Turner-Trauring 5e7cc16c3b
Fix parallel pip cache downloads causing crash (#12364)
Co-authored-by: Itamar Turner-Trauring <itamar@pythonspeed.com>
2023-10-18 23:14:22 +01:00
Stéphane Bidoul 8a0f77c171
Merge pull request #12355 from sbidoul/build-using-build
Build using `build`
2023-10-18 10:05:23 +02:00
Paul Moore f3620cdb5b
Merge pull request #12363 from pfmoore/safe_isoformat
Handle ISO formats with a trailing Z
2023-10-17 12:29:43 +01:00
Paul Moore fb06d12d5a Handle ISO formats with a trailing Z 2023-10-17 11:07:21 +01:00
Stéphane Bidoul 9f213bf69a
Merge pull request #12356 from sbidoul/clarify-changelog
Clarify changelog
2023-10-15 19:24:55 +02:00
Stéphane Bidoul a982c7bc35 Add a few PEP links in the changelog 2023-10-15 19:19:36 +02:00
Stéphane Bidoul e1e227d7d6 Clarify changelog 2023-10-15 18:48:53 +02:00
Stéphane Bidoul 9b0abc8c40 Build using `build`
Update the build-release nox session to build using `build`
instead of a direct setup.py call.
2023-10-15 18:44:34 +02:00
Stéphane Bidoul 9d4be7802f
Merge pull request #12353 from sbidoul/release/23.3
Release/23.3
2023-10-15 18:16:30 +02:00
Stéphane Bidoul 8ffe890dc5
Merge pull request #12354 from sbidoul/imp-release-docs
Minor docs improvements
2023-10-15 11:25:55 +02:00
Stéphane Bidoul c0cce3ca60 Bump for development 2023-10-15 10:23:09 +02:00
Stéphane Bidoul e3dc91dad9 Bump for release 2023-10-15 10:23:02 +02:00
Stéphane Bidoul 3e85558b10 Update AUTHORS.txt 2023-10-15 10:23:01 +02:00
Stéphane Bidoul 8d0278771c Reclassify news fragment
This is not for the process category, and
probably not significant enough for a feature news entry.
2023-10-15 10:22:52 +02:00
Stéphane Bidoul bf9a9cbdae Mention 'skip news' label in docs 2023-10-15 10:20:24 +02:00
Stéphane Bidoul 8ff33edfc5 Don't mention setuptools in release process docs 2023-10-15 10:08:24 +02:00
Stéphane Bidoul f6ecf406c3
Merge pull request #12350 from sbidoul/readact-collecting-url
Redact URLs in Collecting... logs
2023-10-15 10:02:04 +02:00
Stéphane Bidoul 306086513b
Merge pull request #12335 from edmorley/patch-1
Correct issue number for NEWS entry added by #12197
2023-10-14 16:17:29 +02:00
Stéphane Bidoul 8f0ed32413 Redact URLs in Collecting... logs 2023-10-14 14:02:55 +02:00
Ed Morley d1659b87e4 Correct issue number for NEWS entry added by #12197
The NEWS entry added in PR #12197 referenced issue #12191,
however, the issue it actually fixed was #11847.
2023-10-14 10:13:03 +00:00
Paul Moore 2333ef3b53
Upgrade urllib3 to 1.26.17 (#12343) 2023-10-12 13:12:06 +02:00
Damian Shaw 496b268c1b
Update "Running Tests" documentation (#12334)
Co-authored-by: Paul Moore <p.f.moore@gmail.com>
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
2023-10-11 17:36:40 +02:00
Stéphane Bidoul d1f0981cb2
Merge pull request #12331 from sbidoul/update-egg-deprecation-message
Update egg deprecation message
2023-10-09 13:59:39 +02:00
Stéphane Bidoul 441891cdfd
Merge pull request #12332 from sbidoul/postpone-legacy-version-removal
Postpone deprecation of legacy versions and specifiers
2023-10-09 13:59:17 +02:00
Stéphane Bidoul 76a8c0f265 Postpone deprecation of legacy versions and specifiers 2023-10-08 18:17:05 +02:00
Stéphane Bidoul d9b47d0173 Update egg deprecation message 2023-10-08 17:19:09 +02:00
Pradyun Gedam f53f04dd81
Merge pull request #12325 from pypa/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-10-07 20:03:21 +01:00
Pradyun Gedam 53059d316a
Merge pull request #12324 from pypa/dependabot/github_actions/dessant/lock-threads-4
Bump dessant/lock-threads from 3 to 4
2023-10-07 20:02:45 +01:00
dependabot[bot] 0042cc94cc
Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-07 19:00:51 +00:00
dependabot[bot] 431cf5af82
Bump dessant/lock-threads from 3 to 4
Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 3 to 4.
- [Release notes](https://github.com/dessant/lock-threads/releases)
- [Changelog](https://github.com/dessant/lock-threads/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dessant/lock-threads/compare/v3...v4)

---
updated-dependencies:
- dependency-name: dessant/lock-threads
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-07 19:00:48 +00:00
Pradyun Gedam fd608c35cc
Merge pull request #12051 from kurtmckee/add-dependabot-for-actions
Add a dependabot config to update CI actions monthly
2023-10-07 19:57:31 +01:00
Pradyun Gedam 9b7348269b
Fix zsh completion script (#12173) 2023-10-07 14:29:52 +01:00
Wu Zhenyu dba399fe6a Fix #12166
- tests expected results indendation was off
- add bugfix news entry
2023-10-07 15:15:51 +08:00
Kurt McKee ac962890b5
Add a dependabot config to update CI actions monthly 2023-10-06 09:49:52 -05:00
Tzu-ping Chung b551c09c4e
Merge pull request #12095 from sanderr/issue/11924-requirements-on-extras 2023-10-05 15:29:25 +08:00
Stéphane Bidoul dfaac0a688
Merge pull request #12312 from cclauss/patch-1
Wrap long lines
2023-10-03 19:13:31 +02:00
Tzu-ping Chung 8e5df328a8
Merge pull request #12024 from uranusjr/no-import-from-conftest 2023-10-03 17:41:16 +08:00
Tzu-ping Chung 2fad07e6e2 Merge branch 'main' into no-import-from-conftest 2023-10-03 16:11:30 +08:00
Christian Clauss dcb9dc0369 Wrap long lines 2023-10-03 09:13:55 +02:00
Stéphane Bidoul ff05e4224b
Merge pull request #12289 from lgeiger/noqa
Remove outdated `noqa` comments
2023-10-01 19:29:09 +02:00
Stéphane Bidoul cb21251ffc
Merge pull request #12307 from sbidoul/postpone-deprecations
Postpone some deprecation removals
2023-10-01 19:13:05 +02:00
Stéphane Bidoul 1082eb1262
Merge pull request #12306 from pradyunsg/vcs-ref
Use `-r=...` instead of `-r ...` for VCS CLI
2023-10-01 16:59:57 +02:00
Pradyun Gedam 6d4b551ccc
Merge pull request #12303 from sbidoul/drop-isort-flake8-cfg
Drop isort and flake8 settings from setup.cfg
2023-10-01 14:47:15 +01:00
Pradyun Gedam 408b5248dc
📰 2023-10-01 14:45:29 +01:00
Pradyun Gedam 389cb799d0
Use `-r=...` instead of `-r ...` for hg
This ensures that the resulting revision can not be misinterpreted as an
option.
2023-10-01 14:45:29 +01:00
Stéphane Bidoul ccc4bbcdfd Postpone some deprecation removals 2023-10-01 15:05:20 +02:00
Pradyun Gedam 11ff957838
Merge pull request #12292 from hauntsaninja/literal-type
Avoid use of 2020-resolver and legacy-resolver
2023-10-01 13:56:57 +01:00
Pradyun Gedam f6b445be48
Merge pull request #12304 from pradyunsg/better-rich-presentation
Rework how the logging stack handles rich objects
2023-10-01 13:20:19 +01:00
Pradyun Gedam 3f6e81694f
Rework how the logging stack handles rich objects
This makes it possible to render content via rich without a magic string
and relies on a proper mechanism supported by the logging stack.
2023-10-01 12:24:10 +01:00
Stéphane Bidoul 9692d48822 Drop isort and flake8 settings from setup.cfg
Since we use ruff, these are not used anymore.
2023-10-01 11:51:37 +02:00
Stéphane Bidoul 71df02c412
Merge pull request #12199 from jeffwidman/patch-1
Replace python2 deprecation with a badge of supported python versions
2023-10-01 11:22:50 +02:00
Stéphane Bidoul 4ad9b90eb2
Merge pull request #12281 from sbidoul/fix-12280-sbi
Fix git version parsing issue
2023-10-01 11:21:14 +02:00
hauntsaninja 666be3544b Avoid use of 2020-resolver and legacy-resolver 2023-09-23 13:34:06 -07:00
Lukas Geiger 3d6b0be901 Remove outdated noqa comments 2023-09-22 01:28:54 +01:00
Stéphane Bidoul 3b4738cf9a Fix git version parsing issue 2023-09-17 20:06:35 +02:00
Sander Van Balen 0f543e3c7e made assertions more robust 2023-09-13 16:51:54 +02:00
Sander Van Balen 89b68c6bf9 Merge branch 'main' into issue/11924-requirements-on-extras 2023-09-13 15:20:25 +02:00
Sander Van Balen 46707a4225 Merge branch 'issue/11924-requirements-on-extras' of github.com:sanderr/pip into issue/11924-requirements-on-extras 2023-09-13 15:14:17 +02:00
Sander Van Balen ce949466c9 fixed argument name in docstring 2023-09-13 15:14:07 +02:00
Sander Van Balen fbda0a2ba7
Update tests/unit/resolution_resolvelib/test_requirement.py
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
2023-09-08 16:32:42 +02:00
Sander Van Balen 952ab6d837
Update src/pip/_internal/resolution/resolvelib/factory.py
Co-authored-by: Tzu-ping Chung <uranusjr@gmail.com>
2023-09-07 10:31:49 +02:00
Sander Van Balen 449522a828 minor fixes and linting 2023-09-06 15:16:22 +02:00
Sander Van Balen f5602fa0b8 added message to invariant assertions 2023-09-06 15:12:17 +02:00
Sander Van Balen ff9e15d813 Merge branch 'main' into issue/11924-requirements-on-extras 2023-09-06 15:08:02 +02:00
Sander Van Balen 50cd318cef review comment: renamed and moved up ExtrasCandidate._ireq 2023-09-06 15:06:19 +02:00
Sander Van Balen 4e73e3e96e review comment: subclass instead of constructor flag 2023-09-06 14:39:51 +02:00
Sander Van Balen 9041602980 Merge branch 'issue/11924-requirements-on-extras' of github.com:sanderr/pip into issue/11924-requirements-on-extras 2023-09-06 14:25:50 +02:00
Sander Van Balen 5a01679022
Update src/pip/_internal/req/constructors.py
Co-authored-by: Tzu-ping Chung <uranusjr@gmail.com>
2023-09-06 13:54:28 +02:00
Sander Van Balen 0de374e4df review comment: return iterator instead of list 2023-09-06 13:52:41 +02:00
Sander Van Balen 21bfe401a9 use more stable sort key 2023-09-06 11:50:10 +02:00
Sander Van Balen 3f3ae6f24d Merge branch 'main' into issue/11924-requirements-on-extras 2023-09-06 10:24:11 +02:00
Jeff Widman d65ba2f6b6
Replace python2 deprecation with a badge of supported python versions
The python world has (mostly) moved on from Python 2. Anyone not already aware of the py2->py3 migration is probably new to the ecosystem and started on Python 3.

Additionally, it's convenient to see at a glance what versions of Python are supported by the current release. This pulls from PyPI versions, so will not immediately match `main` (we can probably change this to match `main` if preferred).

So by doing this it's both more useful going forward, and also lets us drop the explicit notice about dropping Python 2 support.
2023-08-02 13:54:12 -07:00
Sander Van Balen ba761cdda7 Merge branch 'main' into issue/11924-requirements-on-extras 2023-07-26 15:10:58 +02:00
Sander Van Balen 32e95be213 exclude brackets 2023-07-26 14:31:12 +02:00
Sander Van Balen f4a7c0c569 cleaned up windows fix 2023-07-26 14:12:38 +02:00
Sander Van Balen 504485c276 lint 2023-07-26 14:03:26 +02:00
Sander Van Balen 55e9762873 windows compatibility 2023-07-26 14:02:50 +02:00
Sander Van Balen 6ed231a52b added unit tests for install req manipulation 2023-07-26 11:48:42 +02:00
Sander Van Balen cc909e87e5 reverted unnecessary changes 2023-07-26 11:06:24 +02:00
Sander Van Balen 314d7c1254 simplified regex 2023-07-26 10:53:34 +02:00
Sander Van Balen 6663b89a4d added final bugfix news entry 2023-07-26 10:37:00 +02:00
Sander Van Balen 1207389177 made primary news fragment of type feature 2023-07-26 10:33:43 +02:00
Sander Van Balen e6333bb4d1 linting 2023-07-26 10:32:58 +02:00
Sander Van Balen 39e1102800 fixed minor type errors 2023-07-25 17:33:09 +02:00
Sander Van Balen 292387f20b py37 compatibility 2023-07-25 17:25:05 +02:00
Sander Van Balen dc01a40d41 py38 compatibility 2023-07-25 17:19:21 +02:00
Sander Van Balen 4ae829cb3f news entries 2023-07-25 17:14:50 +02:00
Sander Van Balen fc86308c04 Merge branch 'main' into issue/11924-requirements-on-extras 2023-07-25 16:47:37 +02:00
Sander Van Balen cc6a2bded2 added second report test case 2023-07-25 16:38:51 +02:00
Sander Van Balen e569017351 added test case for report bugfix 2023-07-25 16:34:23 +02:00
Sander Van Balen 3fa373c078 added test for comes-from reporting 2023-07-25 15:59:20 +02:00
Sander Van Balen ff9aeae0d2 added resolver test case 2023-07-25 15:36:33 +02:00
Sander Van Balen 7e8da6176f clarification 2023-07-13 17:02:53 +02:00
Sander Van Balen faa3289a94 use regex for requirement update 2023-07-13 16:40:56 +02:00
Sander Van Balen 8aa17580ed dropped unused attribute 2023-07-13 14:57:18 +02:00
Sander Van Balen 1038f15496 stray todo 2023-06-22 16:24:26 +02:00
Sander Van Balen 3160293193 improvement 2023-06-22 16:10:09 +02:00
Sander Van Balen cb0f97f70e reverted troublesome changes 2023-06-22 15:56:23 +02:00
Sander Van Balen 49027d7de3 cleanup 2023-06-22 14:59:43 +02:00
Sander Van Balen d09431feb5 fixes 2023-06-22 14:42:05 +02:00
Sander Van Balen 5f8f40eb1d refinements 2023-06-22 11:08:33 +02:00
Sander Van Balen 937d8f0b61 partial improvement 2023-06-21 17:34:30 +02:00
Sander Van Balen 5bebe850ea take non-extra requirements into account for extra installs 2023-06-20 17:13:18 +02:00
Tzu-ping Chung c3160c5423 Avoid importing things from conftest
It is generally discouraged to import from conftest. Things are now
moved to tests.lib and imported from there instead.

Also did some cleanup to remove the no-longer-needed nullcontext shim.
2023-05-09 15:43:53 +08:00
150 changed files with 1188 additions and 774 deletions

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

View File

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: "3.x" python-version: "3.x"
@ -57,7 +57,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: "3.x" python-version: "3.x"
@ -81,7 +81,7 @@ jobs:
github.event_name != 'pull_request' github.event_name != 'pull_request'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: "3.x" python-version: "3.x"
@ -112,7 +112,7 @@ jobs:
- "3.12" - "3.12"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
@ -164,7 +164,7 @@ jobs:
group: [1, 2] group: [1, 2]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
@ -215,7 +215,7 @@ jobs:
github.event_name != 'pull_request' github.event_name != 'pull_request'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.10"

View File

@ -17,7 +17,7 @@ jobs:
if: github.repository_owner == 'pypa' if: github.repository_owner == 'pypa'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v3 - uses: dessant/lock-threads@v4
with: with:
issue-inactive-days: '30' issue-inactive-days: '30'
pr-inactive-days: '15' pr-inactive-days: '15'

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
# `towncrier check` runs `git diff --name-only origin/main...`, which # `towncrier check` runs `git diff --name-only origin/main...`, which
# needs a non-shallow clone. # needs a non-shallow clone.

View File

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: RTD Deploys environment: RTD Deploys
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v4
with: with:
python-version: "3.11" python-version: "3.11"

View File

@ -22,26 +22,26 @@ repos:
- id: black - id: black
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. rev: v0.1.4
rev: v0.0.287
hooks: hooks:
- id: ruff - id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.961 rev: v1.6.1
hooks: hooks:
- id: mypy - id: mypy
exclude: tests/data exclude: tests/data
args: ["--pretty", "--show-error-codes"] args: ["--pretty", "--show-error-codes"]
additional_dependencies: [ additional_dependencies: [
'keyring==23.0.1', 'keyring==24.2.0',
'nox==2021.6.12', 'nox==2023.4.22',
'pytest', 'pytest',
'types-docutils==0.18.3', 'types-docutils==0.20.0.3',
'types-setuptools==57.4.14', 'types-setuptools==68.2.0.0',
'types-freezegun==1.1.9', 'types-freezegun==1.1.10',
'types-six==1.16.15', 'types-six==1.16.21.9',
'types-pyyaml==6.0.12.2', 'types-pyyaml==6.0.12.12',
] ]
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks

View File

@ -20,6 +20,7 @@ Albert-Guan
albertg albertg
Alberto Sottile Alberto Sottile
Aleks Bunin Aleks Bunin
Ales Erjavec
Alethea Flowers Alethea Flowers
Alex Gaynor Alex Gaynor
Alex Grönholm Alex Grönholm
@ -30,6 +31,7 @@ Alex Stachowiak
Alexander Shtyrov Alexander Shtyrov
Alexandre Conrad Alexandre Conrad
Alexey Popravka Alexey Popravka
Aleš Erjavec
Alli Alli
Ami Fischman Ami Fischman
Ananya Maiti Ananya Maiti
@ -196,9 +198,11 @@ David Runge
David Tucker David Tucker
David Wales David Wales
Davidovich Davidovich
ddelange
Deepak Sharma Deepak Sharma
Deepyaman Datta Deepyaman Datta
Denise Yu Denise Yu
dependabot[bot]
derwolfe derwolfe
Desetude Desetude
Devesh Kumar Singh Devesh Kumar Singh
@ -312,6 +316,7 @@ Ilya Baryshev
Inada Naoki Inada Naoki
Ionel Cristian Mărieș Ionel Cristian Mărieș
Ionel Maries Cristian Ionel Maries Cristian
Itamar Turner-Trauring
Ivan Pozdeev Ivan Pozdeev
Jacob Kim Jacob Kim
Jacob Walls Jacob Walls
@ -338,6 +343,7 @@ Jay Graves
Jean-Christophe Fillion-Robin Jean-Christophe Fillion-Robin
Jeff Barber Jeff Barber
Jeff Dairiki Jeff Dairiki
Jeff Widman
Jelmer Vernooij Jelmer Vernooij
jenix21 jenix21
Jeremy Stanley Jeremy Stanley
@ -367,6 +373,7 @@ Joseph Long
Josh Bronson Josh Bronson
Josh Hansen Josh Hansen
Josh Schneier Josh Schneier
Joshua
Juan Luis Cano Rodríguez Juan Luis Cano Rodríguez
Juanjo Bazán Juanjo Bazán
Judah Rand Judah Rand
@ -397,6 +404,7 @@ KOLANICH
kpinc kpinc
Krishna Oza Krishna Oza
Kumar McMillan Kumar McMillan
Kurt McKee
Kyle Persohn Kyle Persohn
lakshmanaram lakshmanaram
Laszlo Kiss-Kollar Laszlo Kiss-Kollar
@ -413,6 +421,7 @@ lorddavidiii
Loren Carvalho Loren Carvalho
Lucas Cimon Lucas Cimon
Ludovic Gasc Ludovic Gasc
Lukas Geiger
Lukas Juhrich Lukas Juhrich
Luke Macken Luke Macken
Luo Jiebin Luo Jiebin
@ -529,6 +538,7 @@ Patrick Jenkins
Patrick Lawson Patrick Lawson
patricktokeeffe patricktokeeffe
Patrik Kopkan Patrik Kopkan
Paul Ganssle
Paul Kehrer Paul Kehrer
Paul Moore Paul Moore
Paul Nasrat Paul Nasrat
@ -609,6 +619,7 @@ ryneeverett
Sachi King Sachi King
Salvatore Rinchiera Salvatore Rinchiera
sandeepkiran-js sandeepkiran-js
Sander Van Balen
Savio Jomton Savio Jomton
schlamar schlamar
Scott Kitterman Scott Kitterman
@ -621,6 +632,7 @@ SeongSoo Cho
Sergey Vasilyev Sergey Vasilyev
Seth Michael Larson Seth Michael Larson
Seth Woodworth Seth Woodworth
Shahar Epstein
Shantanu Shantanu
shireenrao shireenrao
Shivansh-007 Shivansh-007
@ -648,6 +660,7 @@ Steve Kowalik
Steven Myint Steven Myint
Steven Silvester Steven Silvester
stonebig stonebig
studioj
Stéphane Bidoul Stéphane Bidoul
Stéphane Bidoul (ACSONE) Stéphane Bidoul (ACSONE)
Stéphane Klein Stéphane Klein

View File

@ -9,13 +9,80 @@
.. towncrier release notes start .. towncrier release notes start
23.3.1 (2023-10-21)
===================
Bug Fixes
---------
- Handle a timezone indicator of Z when parsing dates in the self check. (`#12338 <https://github.com/pypa/pip/issues/12338>`_)
- Fix bug where installing the same package at the same time with multiple pip processes could fail. (`#12361 <https://github.com/pypa/pip/issues/12361>`_)
23.3 (2023-10-15)
=================
Process
-------
- Added reference to `vulnerability reporting guidelines <https://www.python.org/dev/security/>`_ to pip's security policy.
Deprecations and Removals
-------------------------
- Drop a fallback to using SecureTransport on macOS. It was useful when pip detected OpenSSL older than 1.0.1, but the current pip does not support any Python version supporting such old OpenSSL versions. (`#12175 <https://github.com/pypa/pip/issues/12175>`_)
Features
--------
- Improve extras resolution for multiple constraints on same base package. (`#11924 <https://github.com/pypa/pip/issues/11924>`_)
- Improve use of datastructures to make candidate selection 1.6x faster. (`#12204 <https://github.com/pypa/pip/issues/12204>`_)
- Allow ``pip install --dry-run`` to use platform and ABI overriding options. (`#12215 <https://github.com/pypa/pip/issues/12215>`_)
- Add ``is_yanked`` boolean entry to the installation report (``--report``) to indicate whether the requirement was yanked from the index, but was still selected by pip conform to :pep:`592`. (`#12224 <https://github.com/pypa/pip/issues/12224>`_)
Bug Fixes
---------
- Ignore errors in temporary directory cleanup (show a warning instead). (`#11394 <https://github.com/pypa/pip/issues/11394>`_)
- Normalize extras according to :pep:`685` from package metadata in the resolver
for comparison. This ensures extras are correctly compared and merged as long
as the package providing the extra(s) is built with values normalized according
to the standard. Note, however, that this *does not* solve cases where the
package itself contains unnormalized extra values in the metadata. (`#11649 <https://github.com/pypa/pip/issues/11649>`_)
- Prevent downloading sdists twice when :pep:`658` metadata is present. (`#11847 <https://github.com/pypa/pip/issues/11847>`_)
- Include all requested extras in the install report (``--report``). (`#11924 <https://github.com/pypa/pip/issues/11924>`_)
- Removed uses of ``datetime.datetime.utcnow`` from non-vendored code. (`#12005 <https://github.com/pypa/pip/issues/12005>`_)
- Consistently report whether a dependency comes from an extra. (`#12095 <https://github.com/pypa/pip/issues/12095>`_)
- Fix completion script for zsh (`#12166 <https://github.com/pypa/pip/issues/12166>`_)
- Fix improper handling of the new onexc argument of ``shutil.rmtree()`` in Python 3.12. (`#12187 <https://github.com/pypa/pip/issues/12187>`_)
- Filter out yanked links from the available versions error message: "(from versions: 1.0, 2.0, 3.0)" will not contain yanked versions conform PEP 592. The yanked versions (if any) will be mentioned in a separate error message. (`#12225 <https://github.com/pypa/pip/issues/12225>`_)
- Fix crash when the git version number contains something else than digits and dots. (`#12280 <https://github.com/pypa/pip/issues/12280>`_)
- Use ``-r=...`` instead of ``-r ...`` to specify references with Mercurial. (`#12306 <https://github.com/pypa/pip/issues/12306>`_)
- Redact password from URLs in some additional places. (`#12350 <https://github.com/pypa/pip/issues/12350>`_)
- pip uses less memory when caching large packages. As a result, there is a new on-disk cache format stored in a new directory ($PIP_CACHE_DIR/http-v2). (`#2984 <https://github.com/pypa/pip/issues/2984>`_)
Vendored Libraries
------------------
- Upgrade certifi to 2023.7.22
- Add truststore 0.8.0
- Upgrade urllib3 to 1.26.17
Improved Documentation
----------------------
- Document that ``pip search`` support has been removed from PyPI (`#12059 <https://github.com/pypa/pip/issues/12059>`_)
- Clarify --prefer-binary in CLI and docs (`#12122 <https://github.com/pypa/pip/issues/12122>`_)
- Document that using OS-provided Python can cause pip's test suite to report false failures. (`#12334 <https://github.com/pypa/pip/issues/12334>`_)
23.2.1 (2023-07-22) 23.2.1 (2023-07-22)
=================== ===================
Bug Fixes Bug Fixes
--------- ---------
- Disable PEP 658 metadata fetching with the legacy resolver. (`#12156 <https://github.com/pypa/pip/issues/12156>`_) - Disable :pep:`658` metadata fetching with the legacy resolver. (`#12156 <https://github.com/pypa/pip/issues/12156>`_)
23.2 (2023-07-15) 23.2 (2023-07-15)
@ -45,11 +112,11 @@ Bug Fixes
--------- ---------
- Fix ``pip completion --zsh``. (`#11417 <https://github.com/pypa/pip/issues/11417>`_) - Fix ``pip completion --zsh``. (`#11417 <https://github.com/pypa/pip/issues/11417>`_)
- Prevent downloading files twice when PEP 658 metadata is present (`#11847 <https://github.com/pypa/pip/issues/11847>`_) - Prevent downloading files twice when :pep:`658` metadata is present (`#11847 <https://github.com/pypa/pip/issues/11847>`_)
- Add permission check before configuration (`#11920 <https://github.com/pypa/pip/issues/11920>`_) - Add permission check before configuration (`#11920 <https://github.com/pypa/pip/issues/11920>`_)
- Fix deprecation warnings in Python 3.12 for usage of shutil.rmtree (`#11957 <https://github.com/pypa/pip/issues/11957>`_) - Fix deprecation warnings in Python 3.12 for usage of shutil.rmtree (`#11957 <https://github.com/pypa/pip/issues/11957>`_)
- Ignore invalid or unreadable ``origin.json`` files in the cache of locally built wheels. (`#11985 <https://github.com/pypa/pip/issues/11985>`_) - Ignore invalid or unreadable ``origin.json`` files in the cache of locally built wheels. (`#11985 <https://github.com/pypa/pip/issues/11985>`_)
- Fix installation of packages with PEP658 metadata using non-canonicalized names (`#12038 <https://github.com/pypa/pip/issues/12038>`_) - Fix installation of packages with :pep:`658` metadata using non-canonicalized names (`#12038 <https://github.com/pypa/pip/issues/12038>`_)
- Correctly parse ``dist-info-metadata`` values from JSON-format index data. (`#12042 <https://github.com/pypa/pip/issues/12042>`_) - Correctly parse ``dist-info-metadata`` values from JSON-format index data. (`#12042 <https://github.com/pypa/pip/issues/12042>`_)
- Fail with an error if the ``--python`` option is specified after the subcommand name. (`#12067 <https://github.com/pypa/pip/issues/12067>`_) - Fail with an error if the ``--python`` option is specified after the subcommand name. (`#12067 <https://github.com/pypa/pip/issues/12067>`_)
- Fix slowness when using ``importlib.metadata`` (the default way for pip to read metadata in Python 3.11+) and there is a large overlap between already installed and to-be-installed packages. (`#12079 <https://github.com/pypa/pip/issues/12079>`_) - Fix slowness when using ``importlib.metadata`` (the default way for pip to read metadata in Python 3.11+) and there is a large overlap between already installed and to-be-installed packages. (`#12079 <https://github.com/pypa/pip/issues/12079>`_)
@ -220,7 +287,7 @@ Features
- Change the hashes in the installation report to be a mapping. Emit the - Change the hashes in the installation report to be a mapping. Emit the
``archive_info.hashes`` dictionary in ``direct_url.json``. (`#11312 <https://github.com/pypa/pip/issues/11312>`_) ``archive_info.hashes`` dictionary in ``direct_url.json``. (`#11312 <https://github.com/pypa/pip/issues/11312>`_)
- Implement logic to read the ``EXTERNALLY-MANAGED`` file as specified in PEP 668. - Implement logic to read the ``EXTERNALLY-MANAGED`` file as specified in :pep:`668`.
This allows a downstream Python distributor to prevent users from using pip to This allows a downstream Python distributor to prevent users from using pip to
modify the externally managed environment. (`#11381 <https://github.com/pypa/pip/issues/11381>`_) modify the externally managed environment. (`#11381 <https://github.com/pypa/pip/issues/11381>`_)
- Enable the use of ``keyring`` found on ``PATH``. This allows ``keyring`` - Enable the use of ``keyring`` found on ``PATH``. This allows ``keyring``
@ -236,7 +303,7 @@ Bug Fixes
- Use the "venv" scheme if available to obtain prefixed lib paths. (`#11598 <https://github.com/pypa/pip/issues/11598>`_) - Use the "venv" scheme if available to obtain prefixed lib paths. (`#11598 <https://github.com/pypa/pip/issues/11598>`_)
- Deprecated a historical ambiguity in how ``egg`` fragments in URL-style - Deprecated a historical ambiguity in how ``egg`` fragments in URL-style
requirements are formatted and handled. ``egg`` fragments that do not look requirements are formatted and handled. ``egg`` fragments that do not look
like PEP 508 names now produce a deprecation warning. (`#11617 <https://github.com/pypa/pip/issues/11617>`_) like :pep:`508` names now produce a deprecation warning. (`#11617 <https://github.com/pypa/pip/issues/11617>`_)
- Fix scripts path in isolated build environment on Debian. (`#11623 <https://github.com/pypa/pip/issues/11623>`_) - Fix scripts path in isolated build environment on Debian. (`#11623 <https://github.com/pypa/pip/issues/11623>`_)
- Make ``pip show`` show the editable location if package is editable (`#11638 <https://github.com/pypa/pip/issues/11638>`_) - Make ``pip show`` show the editable location if package is editable (`#11638 <https://github.com/pypa/pip/issues/11638>`_)
- Stop checking that ``wheel`` is present when ``build-system.requires`` - Stop checking that ``wheel`` is present when ``build-system.requires``

View File

@ -3,9 +3,15 @@ pip - The Python Package Installer
.. image:: https://img.shields.io/pypi/v/pip.svg .. image:: https://img.shields.io/pypi/v/pip.svg
:target: https://pypi.org/project/pip/ :target: https://pypi.org/project/pip/
:alt: PyPI
.. image:: https://img.shields.io/pypi/pyversions/pip
:target: https://pypi.org/project/pip
:alt: PyPI - Python Version
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest .. image:: https://readthedocs.org/projects/pip/badge/?version=latest
:target: https://pip.pypa.io/en/latest :target: https://pip.pypa.io/en/latest
:alt: Documentation
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes. pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
@ -19,8 +25,6 @@ We release updates regularly, with a new version every 3 months. Find more detai
* `Release notes`_ * `Release notes`_
* `Release process`_ * `Release process`_
**Note**: pip 21.0, in January 2021, removed Python 2 support, per pip's `Python 2 support policy`_. Please migrate to Python 3.
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms: If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
* `Issue tracking`_ * `Issue tracking`_
@ -47,7 +51,6 @@ rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/ .. _Release process: https://pip.pypa.io/en/latest/development/release-process/
.. _GitHub page: https://github.com/pypa/pip .. _GitHub page: https://github.com/pypa/pip
.. _Development documentation: https://pip.pypa.io/en/latest/development .. _Development documentation: https://pip.pypa.io/en/latest/development
.. _Python 2 support policy: https://pip.pypa.io/en/latest/development/release-process/#python-2-support
.. _Issue tracking: https://github.com/pypa/pip/issues .. _Issue tracking: https://github.com/pypa/pip/issues
.. _Discourse channel: https://discuss.python.org/c/packaging .. _Discourse channel: https://discuss.python.org/c/packaging
.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa .. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa

View File

@ -45,8 +45,8 @@ When looking at the items to be installed, pip checks what type of item
each is, in the following order: each is, in the following order:
1. Project or archive URL. 1. Project or archive URL.
2. Local directory (which must contain a ``setup.py``, or pip will report 2. Local directory (which must contain a ``pyproject.toml`` or ``setup.py``,
an error). otherwise pip will report an error).
3. Local file (a sdist or wheel format archive, following the naming 3. Local file (a sdist or wheel format archive, following the naming
conventions for those formats). conventions for those formats).
4. A requirement, as specified in :pep:`440`. 4. A requirement, as specified in :pep:`440`.

View File

@ -112,7 +112,7 @@ the ``news/`` directory with the extension of ``.trivial.rst``. If you are on a
POSIX like operating system, one can be added by running POSIX like operating system, one can be added by running
``touch news/$(uuidgen).trivial.rst``. On Windows, the same result can be ``touch news/$(uuidgen).trivial.rst``. On Windows, the same result can be
achieved in Powershell using ``New-Item "news/$([guid]::NewGuid()).trivial.rst"``. achieved in Powershell using ``New-Item "news/$([guid]::NewGuid()).trivial.rst"``.
Core committers may also add a "trivial" label to the PR which will accomplish Core committers may also add a "skip news" label to the PR which will accomplish
the same thing. the same thing.
Upgrading, removing, or adding a new vendored library gets a special mention Upgrading, removing, or adding a new vendored library gets a special mention

View File

@ -73,7 +73,7 @@ pip's tests are written using the :pypi:`pytest` test framework and
:mod:`unittest.mock`. :pypi:`nox` is used to automate the setup and execution :mod:`unittest.mock`. :pypi:`nox` is used to automate the setup and execution
of pip's tests. of pip's tests.
It is preferable to run the tests in parallel for better experience during development, It is preferable to run the tests in parallel for a better experience during development,
since the tests can take a long time to finish when run sequentially. since the tests can take a long time to finish when run sequentially.
To run tests: To run tests:
@ -104,6 +104,15 @@ can select tests using the various ways that pytest provides:
$ # Using keywords $ # Using keywords
$ nox -s test-3.10 -- -k "install and not wheel" $ nox -s test-3.10 -- -k "install and not wheel"
.. note::
When running pip's tests with OS distribution Python versions, be aware that some
functional tests may fail due to potential patches introduced by the distribution.
For all tests to pass consider:
- Installing Python from `python.org`_ or compile from source
- Or, using `pyenv`_ to assist with source compilation
Running pip's entire test suite requires supported version control tools Running pip's entire test suite requires supported version control tools
(subversion, bazaar, git, and mercurial) to be installed. If you are missing (subversion, bazaar, git, and mercurial) to be installed. If you are missing
any of these VCS, those tests should be skipped automatically. You can also any of these VCS, those tests should be skipped automatically. You can also
@ -114,6 +123,9 @@ explicitly tell pytest to skip those tests:
$ nox -s test-3.10 -- -k "not svn" $ nox -s test-3.10 -- -k "not svn"
$ nox -s test-3.10 -- -k "not (svn or git)" $ nox -s test-3.10 -- -k "not (svn or git)"
.. _python.org: https://www.python.org/downloads/
.. _pyenv: https://github.com/pyenv/pyenv
Running Linters Running Linters
=============== ===============

View File

@ -145,8 +145,8 @@ Creating a new release
#. Push the tag created by ``prepare-release``. #. Push the tag created by ``prepare-release``.
#. Regenerate the ``get-pip.py`` script in the `get-pip repository`_ (as #. Regenerate the ``get-pip.py`` script in the `get-pip repository`_ (as
documented there) and commit the results. documented there) and commit the results.
#. Submit a Pull Request to `CPython`_ adding the new version of pip (and upgrading #. Submit a Pull Request to `CPython`_ adding the new version of pip
setuptools) to ``Lib/ensurepip/_bundled``, removing the existing version, and to ``Lib/ensurepip/_bundled``, removing the existing version, and
adjusting the versions listed in ``Lib/ensurepip/__init__.py``. adjusting the versions listed in ``Lib/ensurepip/__init__.py``.

View File

@ -19,8 +19,8 @@ and how they are related to pip's various command line options.
## Configuration Files ## Configuration Files
Configuration files can change the default values for command line option. Configuration files can change the default values for command line options.
They are written using a standard INI style configuration files. The files are written using standard INI format.
pip has 3 "levels" of configuration files: pip has 3 "levels" of configuration files:
@ -28,11 +28,15 @@ pip has 3 "levels" of configuration files:
- `user`: per-user configuration file. - `user`: per-user configuration file.
- `site`: per-environment configuration file; i.e. per-virtualenv. - `site`: per-environment configuration file; i.e. per-virtualenv.
Additionally, environment variables can be specified which will override any of the above.
### Location ### Location
pip's configuration files are located in fairly standard locations. This pip's configuration files are located in fairly standard locations. This
location is different on different operating systems, and has some additional location is different on different operating systems, and has some additional
complexity for backwards compatibility reasons. complexity for backwards compatibility reasons. Note that if user config files
exist in both the legacy and current locations, values in the current file
will override values in the legacy file.
```{tab} Unix ```{tab} Unix
@ -88,9 +92,10 @@ Site
### `PIP_CONFIG_FILE` ### `PIP_CONFIG_FILE`
Additionally, the environment variable `PIP_CONFIG_FILE` can be used to specify Additionally, the environment variable `PIP_CONFIG_FILE` can be used to specify
a configuration file that's loaded first, and whose values are overridden by a configuration file that's loaded last, and whose values override the values
the values set in the aforementioned files. Setting this to {any}`os.devnull` set in the aforementioned files. Setting this to {any}`os.devnull`
disables the loading of _all_ configuration files. disables the loading of _all_ configuration files. Note that if a file exists
at the location that this is set to, the user config file will not be loaded.
(config-precedence)= (config-precedence)=
@ -99,10 +104,10 @@ disables the loading of _all_ configuration files.
When multiple configuration files are found, pip combines them in the following When multiple configuration files are found, pip combines them in the following
order: order:
- `PIP_CONFIG_FILE`, if given.
- Global - Global
- User - User
- Site - Site
- `PIP_CONFIG_FILE`, if given.
Each file read overrides any values read from previous files, so if the Each file read overrides any values read from previous files, so if the
global timeout is specified in both the global file and the per-user file global timeout is specified in both the global file and the per-user file

View File

@ -194,22 +194,17 @@ class PipReqFileOptionsReference(PipOptions):
opt = option() opt = option()
opt_name = opt._long_opts[0] opt_name = opt._long_opts[0]
if opt._short_opts: if opt._short_opts:
short_opt_name = "{}, ".format(opt._short_opts[0]) short_opt_name = f"{opt._short_opts[0]}, "
else: else:
short_opt_name = "" short_opt_name = ""
if option in cmdoptions.general_group["options"]: if option in cmdoptions.general_group["options"]:
prefix = "" prefix = ""
else: else:
prefix = "{}_".format(self.determine_opt_prefix(opt_name)) prefix = f"{self.determine_opt_prefix(opt_name)}_"
self.view_list.append( self.view_list.append(
"* :ref:`{short}{long}<{prefix}{opt_name}>`".format( f"* :ref:`{short_opt_name}{opt_name}<{prefix}{opt_name}>`",
short=short_opt_name,
long=opt_name,
prefix=prefix,
opt_name=opt_name,
),
"\n", "\n",
) )

View File

@ -1 +0,0 @@
Ignore errors in temporary directory cleanup (show a warning instead).

View File

@ -1,5 +0,0 @@
Normalize extras according to :pep:`685` from package metadata in the resolver
for comparison. This ensures extras are correctly compared and merged as long
as the package providing the extra(s) is built with values normalized according
to the standard. Note, however, that this *does not* solve cases where the
package itself contains unnormalized extra values in the metadata.

1
news/11815.doc.rst Normal file
View File

@ -0,0 +1 @@
Fix explanation of how PIP_CONFIG_FILE works

View File

@ -1 +0,0 @@
Removed uses of ``datetime.datetime.utcnow`` from non-vendored code.

View File

@ -1 +0,0 @@
Document that ``pip search`` support has been removed from PyPI

View File

@ -1 +0,0 @@
Clarify --prefer-binary in CLI and docs

View File

@ -1,6 +0,0 @@
The metadata-fetching log message is moved to the VERBOSE level and now hidden
by default. The more significant information in this message to most users are
already available in surrounding logs (the package name and version of the
metadata being fetched), while the URL to the exact metadata file is generally
too long and clutters the output. The message can be brought back with
``--verbose``.

View File

@ -1 +0,0 @@
Drop a fallback to using SecureTransport on macOS. It was useful when pip detected OpenSSL older than 1.0.1, but the current pip does not support any Python version supporting such old OpenSSL versions.

View File

@ -1 +0,0 @@
Add test cases for some behaviors of ``install --dry-run`` and ``--use-feature=fast-deps``.

View File

@ -1 +0,0 @@
Fix improper handling of the new onexc argument of ``shutil.rmtree()`` in Python 3.12.

View File

@ -1 +0,0 @@
Prevent downloading sdists twice when PEP 658 metadata is present.

View File

@ -1 +0,0 @@
Add lots of comments to the ``BuildTracker``.

View File

@ -1 +0,0 @@
Improve use of datastructures to make candidate selection 1.6x faster

View File

@ -1 +0,0 @@
Allow ``pip install --dry-run`` to use platform and ABI overriding options similar to ``--target``.

View File

@ -1 +0,0 @@
Add ``is_yanked`` boolean entry to the installation report (``--report``) to indicate whether the requirement was yanked from the index, but was still selected by pip conform to PEP 592.

View File

@ -1 +0,0 @@
Filter out yanked links from the available versions error message: "(from versions: 1.0, 2.0, 3.0)" will not contain yanked versions conform PEP 592. The yanked versions (if any) will be mentioned in a separate error message.

View File

@ -1 +0,0 @@
Added reference to `vulnerability reporting guidelines <https://www.python.org/dev/security/>`_ to pip's security policy.

View File

1
news/12389.bugfix.rst Normal file
View File

@ -0,0 +1 @@
Update mypy to 1.6.1 and fix/ignore types

1
news/12390.trivial.rst Normal file
View File

@ -0,0 +1 @@
Update ruff versions and config for dev

1
news/12393.trivial.rst Normal file
View File

@ -0,0 +1 @@
Enforce and update code to use f-strings via Ruff rule UP032

1
news/12417.doc.rst Normal file
View File

@ -0,0 +1 @@
Fix outdated pip install argument description in documentation.

View File

@ -1 +0,0 @@
pip uses less memory when caching large packages. As a result, there is a new on-disk cache format stored in a new directory ($PIP_CACHE_DIR/http-v2).

View File

@ -0,0 +1 @@
Fix mercurial revision "parse error": use ``--rev={ref}`` instead of ``-r={ref}``

View File

@ -1 +0,0 @@
Add ruff rules ASYNC,C4,C90,PERF,PLE,PLR for minor optimizations and to set upper limits on code complexity.

View File

@ -1 +0,0 @@
Upgrade certifi to 2023.7.22

View File

@ -1 +0,0 @@
Add truststore 0.8.0

View File

@ -322,7 +322,7 @@ def build_release(session: nox.Session) -> None:
) )
session.log("# Install dependencies") session.log("# Install dependencies")
session.install("setuptools", "wheel", "twine") session.install("build", "twine")
with release.isolated_temporary_checkout(session, version) as build_dir: with release.isolated_temporary_checkout(session, version) as build_dir:
session.log( session.log(
@ -358,8 +358,7 @@ def build_dists(session: nox.Session) -> List[str]:
) )
session.log("# Build distributions") session.log("# Build distributions")
session.install("setuptools", "wheel") session.run("python", "-m", "build", silent=True)
session.run("python", "setup.py", "sdist", "bdist_wheel", silent=True)
produced_dists = glob.glob("dist/*") produced_dists = glob.glob("dist/*")
session.log(f"# Verify distributions: {', '.join(produced_dists)}") session.log(f"# Verify distributions: {', '.join(produced_dists)}")

View File

@ -84,8 +84,8 @@ ignore = [
"B020", "B020",
"B904", # Ruff enables opinionated warnings by default "B904", # Ruff enables opinionated warnings by default
"B905", # Ruff enables opinionated warnings by default "B905", # Ruff enables opinionated warnings by default
"G202",
] ]
target-version = "py37"
line-length = 88 line-length = 88
select = [ select = [
"ASYNC", "ASYNC",
@ -101,6 +101,8 @@ select = [
"PLE", "PLE",
"PLR0", "PLR0",
"W", "W",
"RUF100",
"UP032",
] ]
[tool.ruff.isort] [tool.ruff.isort]

View File

@ -1,39 +1,3 @@
[isort]
profile = black
skip =
./build,
.nox,
.tox,
.scratch,
_vendor,
data
known_third_party =
pip._vendor
[flake8]
max-line-length = 88
exclude =
./build,
.nox,
.tox,
.scratch,
_vendor,
data
enable-extensions = G
extend-ignore =
G200, G202,
# black adds spaces around ':'
E203,
# using a cache
B019,
# reassigning variables in a loop
B020,
per-file-ignores =
# G: The plugin logging-format treats every .log and .error as logging.
noxfile.py: G
# B011: Do not call assert False since python -O removes these calls
tests/*: B011
[mypy] [mypy]
mypy_path = $MYPY_CONFIG_FILE_DIR/src mypy_path = $MYPY_CONFIG_FILE_DIR/src

View File

@ -77,7 +77,7 @@ setup(
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"pip=pip._internal.cli.main:main", "pip=pip._internal.cli.main:main",
"pip{}=pip._internal.cli.main:main".format(sys.version_info[0]), f"pip{sys.version_info[0]}=pip._internal.cli.main:main",
"pip{}.{}=pip._internal.cli.main:main".format(*sys.version_info[:2]), "pip{}.{}=pip._internal.cli.main:main".format(*sys.version_info[:2]),
], ],
}, },

View File

@ -1,6 +1,6 @@
from typing import List, Optional from typing import List, Optional
__version__ = "23.3.dev0" __version__ = "24.0.dev0"
def main(args: Optional[List[str]] = None) -> int: def main(args: Optional[List[str]] = None) -> int:

View File

@ -181,7 +181,7 @@ class Command(CommandContextMixIn):
assert isinstance(status, int) assert isinstance(status, int)
return status return status
except DiagnosticPipError as exc: except DiagnosticPipError as exc:
logger.error("[present-rich] %s", exc) logger.error("%s", exc, extra={"rich": True})
logger.debug("Exception information:", exc_info=True) logger.debug("Exception information:", exc_info=True)
return ERROR return ERROR

View File

@ -582,10 +582,7 @@ def _handle_python_version(
""" """
version_info, error_msg = _convert_python_version(value) version_info, error_msg = _convert_python_version(value)
if error_msg is not None: if error_msg is not None:
msg = "invalid --python-version value: {!r}: {}".format( msg = f"invalid --python-version value: {value!r}: {error_msg}"
value,
error_msg,
)
raise_option_error(parser, option=option, msg=msg) raise_option_error(parser, option=option, msg=msg)
parser.values.python_version = version_info parser.values.python_version = version_info
@ -826,7 +823,7 @@ def _handle_config_settings(
) -> None: ) -> None:
key, sep, val = value.partition("=") key, sep, val = value.partition("=")
if sep != "=": if sep != "=":
parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") # noqa parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL")
dest = getattr(parser.values, option.dest) dest = getattr(parser.values, option.dest)
if dest is None: if dest is None:
dest = {} dest = {}
@ -921,13 +918,13 @@ def _handle_merge_hash(
algo, digest = value.split(":", 1) algo, digest = value.split(":", 1)
except ValueError: except ValueError:
parser.error( parser.error(
"Arguments to {} must be a hash name " # noqa f"Arguments to {opt_str} must be a hash name "
"followed by a value, like --hash=sha256:" "followed by a value, like --hash=sha256:"
"abcde...".format(opt_str) "abcde..."
) )
if algo not in STRONG_HASHES: if algo not in STRONG_HASHES:
parser.error( parser.error(
"Allowed hash algorithms for {} are {}.".format( # noqa "Allowed hash algorithms for {} are {}.".format(
opt_str, ", ".join(STRONG_HASHES) opt_str, ", ".join(STRONG_HASHES)
) )
) )

View File

@ -229,9 +229,9 @@ class ConfigOptionParser(CustomOptionParser):
val = strtobool(val) val = strtobool(val)
except ValueError: except ValueError:
self.error( self.error(
"{} is not a valid value for {} option, " # noqa f"{val} is not a valid value for {key} option, "
"please specify a boolean value like yes/no, " "please specify a boolean value like yes/no, "
"true/false or 1/0 instead.".format(val, key) "true/false or 1/0 instead."
) )
elif option.action == "count": elif option.action == "count":
with suppress(ValueError): with suppress(ValueError):
@ -240,10 +240,10 @@ class ConfigOptionParser(CustomOptionParser):
val = int(val) val = int(val)
if not isinstance(val, int) or val < 0: if not isinstance(val, int) or val < 0:
self.error( self.error(
"{} is not a valid value for {} option, " # noqa f"{val} is not a valid value for {key} option, "
"please instead specify either a non-negative integer " "please instead specify either a non-negative integer "
"or a boolean value like yes/no or false/true " "or a boolean value like yes/no or false/true "
"which is equivalent to 1/0.".format(val, key) "which is equivalent to 1/0."
) )
elif option.action == "append": elif option.action == "append":
val = val.split() val = val.split()

View File

@ -265,7 +265,7 @@ class RequirementCommand(IndexGroupCommand):
if "legacy-resolver" in options.deprecated_features_enabled: if "legacy-resolver" in options.deprecated_features_enabled:
return "legacy" return "legacy"
return "2020-resolver" return "resolvelib"
@classmethod @classmethod
def make_requirement_preparer( def make_requirement_preparer(
@ -287,7 +287,7 @@ class RequirementCommand(IndexGroupCommand):
legacy_resolver = False legacy_resolver = False
resolver_variant = cls.determine_resolver_variant(options) resolver_variant = cls.determine_resolver_variant(options)
if resolver_variant == "2020-resolver": if resolver_variant == "resolvelib":
lazy_wheel = "fast-deps" in options.features_enabled lazy_wheel = "fast-deps" in options.features_enabled
if lazy_wheel: if lazy_wheel:
logger.warning( logger.warning(
@ -349,7 +349,7 @@ class RequirementCommand(IndexGroupCommand):
# The long import name and duplicated invocation is needed to convince # The long import name and duplicated invocation is needed to convince
# Mypy into correctly typechecking. Otherwise it would complain the # Mypy into correctly typechecking. Otherwise it would complain the
# "Resolver" class being redefined. # "Resolver" class being redefined.
if resolver_variant == "2020-resolver": if resolver_variant == "resolvelib":
import pip._internal.resolution.resolvelib.resolver import pip._internal.resolution.resolvelib.resolver
return pip._internal.resolution.resolvelib.resolver.Resolver( return pip._internal.resolution.resolvelib.resolver.Resolver(

View File

@ -175,7 +175,7 @@ class CacheCommand(Command):
files += self._find_http_files(options) files += self._find_http_files(options)
else: else:
# Add the pattern to the log message # Add the pattern to the log message
no_matching_msg += ' for pattern "{}"'.format(args[0]) no_matching_msg += f' for pattern "{args[0]}"'
if not files: if not files:
logger.warning(no_matching_msg) logger.warning(no_matching_msg)

View File

@ -23,9 +23,18 @@ COMPLETION_SCRIPTS = {
""", """,
"zsh": """ "zsh": """
#compdef -P pip[0-9.]# #compdef -P pip[0-9.]#
compadd $( COMP_WORDS="$words[*]" \\ __pip() {{
COMP_CWORD=$((CURRENT-1)) \\ compadd $( COMP_WORDS="$words[*]" \\
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ) COMP_CWORD=$((CURRENT-1)) \\
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )
}}
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
# autoload from fpath, call function directly
__pip "$@"
else
# eval/source/. command, register function for later
compdef __pip -P 'pip[0-9.]#'
fi
""", """,
"fish": """ "fish": """
function __fish_complete_pip function __fish_complete_pip

View File

@ -242,17 +242,15 @@ class ConfigurationCommand(Command):
e.filename = editor e.filename = editor
raise raise
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise PipError( raise PipError(f"Editor Subprocess exited with exit code {e.returncode}")
"Editor Subprocess exited with exit code {}".format(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""" """Helper to make sure the command got the right number of arguments"""
if len(args) != n: if len(args) != n:
msg = ( msg = (
"Got unexpected number of arguments, expected {}. " f"Got unexpected number of arguments, expected {n}. "
'(example: "{} config {}")' f'(example: "{get_prog()} config {example}")'
).format(n, get_prog(), example) )
raise PipError(msg) raise PipError(msg)
if n == 1: if n == 1:

View File

@ -95,7 +95,7 @@ def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
elif parse_version(actual_version) != parse_version(expected_version): elif parse_version(actual_version) != parse_version(expected_version):
extra_message = ( extra_message = (
" (CONFLICT: vendor.txt suggests version should" " (CONFLICT: vendor.txt suggests version should"
" be {})".format(expected_version) f" be {expected_version})"
) )
logger.info("%s==%s%s", module_name, actual_version, extra_message) logger.info("%s==%s%s", module_name, actual_version, extra_message)
@ -120,7 +120,7 @@ def show_tags(options: Values) -> None:
if formatted_target: if formatted_target:
suffix = f" (target: {formatted_target})" suffix = f" (target: {formatted_target})"
msg = "Compatible tags: {}{}".format(len(tags), suffix) msg = f"Compatible tags: {len(tags)}{suffix}"
logger.info(msg) logger.info(msg)
if options.verbose < 1 and len(tags) > tag_limit: if options.verbose < 1 and len(tags) > tag_limit:
@ -134,16 +134,12 @@ def show_tags(options: Values) -> None:
logger.info(str(tag)) logger.info(str(tag))
if tags_limited: if tags_limited:
msg = ( msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
).format(tag_limit=tag_limit)
logger.info(msg) logger.info(msg)
def ca_bundle_info(config: Configuration) -> str: def ca_bundle_info(config: Configuration) -> str:
# Ruff misidentifies config as a dict. levels = {key.split(".", 1)[0] for key, _ in config.items()}
# Configuration does not have support the mapping interface.
levels = {key.split(".", 1)[0] for key, _ in config.items()} # noqa: PERF102
if not levels: if not levels:
return "Not specified" return "Not specified"

View File

@ -128,12 +128,12 @@ class IndexCommand(IndexGroupCommand):
if not versions: if not versions:
raise DistributionNotFound( raise DistributionNotFound(
"No matching distribution found for {}".format(query) f"No matching distribution found for {query}"
) )
formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)] formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
latest = formatted_versions[0] latest = formatted_versions[0]
write_output("{} ({})".format(query, latest)) write_output(f"{query} ({latest})")
write_output("Available versions: {}".format(", ".join(formatted_versions))) write_output("Available versions: {}".format(", ".join(formatted_versions)))
print_dist_installation_info(query, latest) print_dist_installation_info(query, latest)

View File

@ -501,7 +501,7 @@ class InstallCommand(RequirementCommand):
show_traceback, show_traceback,
options.use_user_site, options.use_user_site,
) )
logger.error(message, exc_info=show_traceback) # noqa logger.error(message, exc_info=show_traceback)
return ERROR return ERROR
@ -595,7 +595,7 @@ class InstallCommand(RequirementCommand):
"source of the following dependency conflicts." "source of the following dependency conflicts."
) )
else: else:
assert resolver_variant == "2020-resolver" assert resolver_variant == "resolvelib"
parts.append( parts.append(
"pip's dependency resolver does not currently take into account " "pip's dependency resolver does not currently take into account "
"all the packages that are installed. This behaviour is the " "all the packages that are installed. This behaviour is the "
@ -607,12 +607,8 @@ class InstallCommand(RequirementCommand):
version = package_set[project_name][0] version = package_set[project_name][0]
for dependency in missing[project_name]: for dependency in missing[project_name]:
message = ( message = (
"{name} {version} requires {requirement}, " f"{project_name} {version} requires {dependency[1]}, "
"which is not installed." "which is not installed."
).format(
name=project_name,
version=version,
requirement=dependency[1],
) )
parts.append(message) parts.append(message)
@ -628,7 +624,7 @@ class InstallCommand(RequirementCommand):
requirement=req, requirement=req,
dep_name=dep_name, dep_name=dep_name,
dep_version=dep_version, dep_version=dep_version,
you=("you" if resolver_variant == "2020-resolver" else "you'll"), you=("you" if resolver_variant == "resolvelib" else "you'll"),
) )
parts.append(message) parts.append(message)

View File

@ -59,8 +59,8 @@ def _disassemble_key(name: str) -> List[str]:
if "." not in name: if "." not in name:
error_message = ( error_message = (
"Key does not contain dot separated section and key. " "Key does not contain dot separated section and key. "
"Perhaps you wanted to use 'global.{}' instead?" f"Perhaps you wanted to use 'global.{name}' instead?"
).format(name) )
raise ConfigurationError(error_message) raise ConfigurationError(error_message)
return name.split(".", 1) return name.split(".", 1)
@ -327,33 +327,35 @@ class Configuration:
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. """Yields variant and configuration files associated with it.
This should be treated like items of a dictionary. This should be treated like items of a dictionary. The order
here doesn't affect what gets overridden. That is controlled
by OVERRIDE_ORDER. However this does control the order they are
displayed to the user. It's probably most ergononmic to display
things in the same order as OVERRIDE_ORDER
""" """
# SMELL: Move the conditions out of this function # SMELL: Move the conditions out of this function
# environment variables have the lowest priority env_config_file = os.environ.get("PIP_CONFIG_FILE", None)
config_file = os.environ.get("PIP_CONFIG_FILE", None)
if config_file is not None:
yield kinds.ENV, [config_file]
else:
yield kinds.ENV, []
config_files = get_configuration_files() config_files = get_configuration_files()
# at the base we have any global configuration
yield kinds.GLOBAL, config_files[kinds.GLOBAL] yield kinds.GLOBAL, config_files[kinds.GLOBAL]
# per-user configuration next # per-user config is not loaded when env_config_file exists
should_load_user_config = not self.isolated and not ( should_load_user_config = not self.isolated and not (
config_file and os.path.exists(config_file) env_config_file and os.path.exists(env_config_file)
) )
if should_load_user_config: if should_load_user_config:
# The legacy config file is overridden by the new config file # The legacy config file is overridden by the new config file
yield kinds.USER, config_files[kinds.USER] yield kinds.USER, config_files[kinds.USER]
# finally virtualenv configuration first trumping others # virtualenv config
yield kinds.SITE, config_files[kinds.SITE] yield kinds.SITE, config_files[kinds.SITE]
if env_config_file is not None:
yield kinds.ENV, [env_config_file]
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""" """Get values present in a config file"""
return self._config[variant] return self._config[variant]

View File

@ -247,10 +247,7 @@ class NoneMetadataError(PipError):
def __str__(self) -> str: def __str__(self) -> str:
# Use `dist` in the error message because its stringification # Use `dist` in the error message because its stringification
# includes more information, like the version and location. # includes more information, like the version and location.
return "None {} metadata found for distribution: {}".format( return f"None {self.metadata_name} metadata found for distribution: {self.dist}"
self.metadata_name,
self.dist,
)
class UserInstallationInvalid(InstallationError): class UserInstallationInvalid(InstallationError):
@ -594,7 +591,7 @@ class HashMismatch(HashError):
self.gots = gots self.gots = gots
def body(self) -> str: def body(self) -> str:
return " {}:\n{}".format(self._requirement_name(), self._hash_comparison()) return f" {self._requirement_name()}:\n{self._hash_comparison()}"
def _hash_comparison(self) -> str: def _hash_comparison(self) -> str:
""" """
@ -616,11 +613,9 @@ class HashMismatch(HashError):
lines: List[str] = [] lines: List[str] = []
for hash_name, expecteds in self.allowed.items(): for hash_name, expecteds in self.allowed.items():
prefix = hash_then_or(hash_name) prefix = hash_then_or(hash_name)
lines.extend( lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)
(" Expected {} {}".format(next(prefix), e)) for e in expecteds
)
lines.append( lines.append(
" Got {}\n".format(self.gots[hash_name].hexdigest()) f" Got {self.gots[hash_name].hexdigest()}\n"
) )
return "\n".join(lines) return "\n".join(lines)

View File

@ -533,8 +533,8 @@ class CandidateEvaluator:
) )
except ValueError: except ValueError:
raise UnsupportedWheel( raise UnsupportedWheel(
"{} is not a supported wheel for this platform. It " f"{wheel.filename} is not a supported wheel for this platform. It "
"can't be sorted.".format(wheel.filename) "can't be sorted."
) )
if self._prefer_binary: if self._prefer_binary:
binary_preference = 1 binary_preference = 1
@ -939,9 +939,7 @@ class PackageFinder:
_format_versions(best_candidate_result.iter_all()), _format_versions(best_candidate_result.iter_all()),
) )
raise DistributionNotFound( raise DistributionNotFound(f"No matching distribution found for {req}")
"No matching distribution found for {}".format(req)
)
def _should_install_candidate( def _should_install_candidate(
candidate: Optional[InstallationCandidate], candidate: Optional[InstallationCandidate],

View File

@ -56,8 +56,7 @@ def distutils_scheme(
try: try:
d.parse_config_files() d.parse_config_files()
except UnicodeDecodeError: except UnicodeDecodeError:
# Typeshed does not include find_config_files() for some reason. paths = d.find_config_files()
paths = d.find_config_files() # type: ignore
logger.warning( logger.warning(
"Ignore distutils configs in %s due to encoding errors.", "Ignore distutils configs in %s due to encoding errors.",
", ".join(os.path.basename(p) for p in paths), ", ".join(os.path.basename(p) for p in paths),

View File

@ -64,10 +64,10 @@ def msg_to_json(msg: Message) -> Dict[str, Any]:
key = json_name(field) key = json_name(field)
if multi: if multi:
value: Union[str, List[str]] = [ value: Union[str, List[str]] = [
sanitise_header(v) for v in msg.get_all(field) sanitise_header(v) for v in msg.get_all(field) # type: ignore
] ]
else: else:
value = sanitise_header(msg.get(field)) value = sanitise_header(msg.get(field)) # type: ignore
if key == "keywords": if key == "keywords":
# Accept both comma-separated and space-separated # Accept both comma-separated and space-separated
# forms, for better compatibility with old data. # forms, for better compatibility with old data.

View File

@ -151,7 +151,8 @@ def _emit_egg_deprecation(location: Optional[str]) -> None:
deprecated( deprecated(
reason=f"Loading egg at {location} is deprecated.", reason=f"Loading egg at {location} is deprecated.",
replacement="to use pip for package installation.", replacement="to use pip for package installation.",
gone_in="23.3", gone_in="24.3",
issue=12330,
) )

View File

@ -27,8 +27,4 @@ class InstallationCandidate(KeyBasedCompareMixin):
) )
def __str__(self) -> str: def __str__(self) -> str:
return "{!r} candidate (version {} at {})".format( return f"{self.name!r} candidate (version {self.version} at {self.link})"
self.name,
self.version,
self.link,
)

View File

@ -31,9 +31,7 @@ def _get(
value = d[key] value = d[key]
if not isinstance(value, expected_type): if not isinstance(value, expected_type):
raise DirectUrlValidationError( raise DirectUrlValidationError(
"{!r} has unexpected type for {} (expected {})".format( f"{value!r} has unexpected type for {key} (expected {expected_type})"
value, key, expected_type
)
) )
return value return value

View File

@ -33,9 +33,7 @@ class FormatControl:
return all(getattr(self, k) == getattr(other, k) for k in self.__slots__) return all(getattr(self, k) == getattr(other, k) for k in self.__slots__)
def __repr__(self) -> str: def __repr__(self) -> str:
return "{}({}, {})".format( return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})"
self.__class__.__name__, self.no_binary, self.only_binary
)
@staticmethod @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:

View File

@ -368,9 +368,7 @@ class Link(KeyBasedCompareMixin):
else: else:
rp = "" rp = ""
if self.comes_from: if self.comes_from:
return "{} (from {}){}".format( return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}"
redact_auth_from_url(self._url), self.comes_from, rp
)
else: else:
return redact_auth_from_url(str(self._url)) return redact_auth_from_url(str(self._url))

View File

@ -33,6 +33,18 @@ class SafeFileCache(SeparateBodyBaseCache):
""" """
A file based cache which is safe to use even when the target directory may A file based cache which is safe to use even when the target directory may
not be accessible or writable. not be accessible or writable.
There is a race condition when two processes try to write and/or read the
same entry at the same time, since each entry consists of two separate
files (https://github.com/psf/cachecontrol/issues/324). We therefore have
additional logic that makes sure that both files to be present before
returning an entry; this fixes the read side of the race condition.
For the write side, we assume that the server will only ever return the
same data for the same URL, which ought to be the case for files pip is
downloading. PyPI does not have a mechanism to swap out a wheel for
another wheel, for example. If this assumption is not true, the
CacheControl issue will need to be fixed.
""" """
def __init__(self, directory: str) -> None: def __init__(self, directory: str) -> None:
@ -49,9 +61,13 @@ class SafeFileCache(SeparateBodyBaseCache):
return os.path.join(self.directory, *parts) return os.path.join(self.directory, *parts)
def get(self, key: str) -> Optional[bytes]: def get(self, key: str) -> Optional[bytes]:
path = self._get_cache_path(key) # The cache entry is only valid if both metadata and body exist.
metadata_path = self._get_cache_path(key)
body_path = metadata_path + ".body"
if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
return None
with suppressed_cache_errors(): with suppressed_cache_errors():
with open(path, "rb") as f: with open(metadata_path, "rb") as f:
return f.read() return f.read()
def _write(self, path: str, data: bytes) -> None: def _write(self, path: str, data: bytes) -> None:
@ -77,9 +93,13 @@ class SafeFileCache(SeparateBodyBaseCache):
os.remove(path + ".body") os.remove(path + ".body")
def get_body(self, key: str) -> Optional[BinaryIO]: def get_body(self, key: str) -> Optional[BinaryIO]:
path = self._get_cache_path(key) + ".body" # The cache entry is only valid if both metadata and body exist.
metadata_path = self._get_cache_path(key)
body_path = metadata_path + ".body"
if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
return None
with suppressed_cache_errors(): with suppressed_cache_errors():
return open(path, "rb") return open(body_path, "rb")
def set_body(self, key: str, body: bytes) -> None: def set_body(self, key: str, body: bytes) -> None:
path = self._get_cache_path(key) + ".body" path = self._get_cache_path(key) + ".body"

View File

@ -42,7 +42,7 @@ def _prepare_download(
logged_url = redact_auth_from_url(url) logged_url = redact_auth_from_url(url)
if total_length: if total_length:
logged_url = "{} ({})".format(logged_url, format_size(total_length)) logged_url = f"{logged_url} ({format_size(total_length)})"
if is_from_cache(resp): if is_from_cache(resp):
logger.info("Using cached %s", logged_url) logger.info("Using cached %s", logged_url)

View File

@ -13,6 +13,8 @@ from pip._internal.network.utils import raise_for_status
if TYPE_CHECKING: if TYPE_CHECKING:
from xmlrpc.client import _HostType, _Marshallable from xmlrpc.client import _HostType, _Marshallable
from _typeshed import SizedBuffer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -33,7 +35,7 @@ class PipXmlrpcTransport(xmlrpc.client.Transport):
self, self,
host: "_HostType", host: "_HostType",
handler: str, handler: str,
request_body: bytes, request_body: "SizedBuffer",
verbose: bool = False, verbose: bool = False,
) -> Tuple["_Marshallable", ...]: ) -> Tuple["_Marshallable", ...]:
assert isinstance(host, str) assert isinstance(host, str)

View File

@ -168,7 +168,7 @@ def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
f"release a version with a conforming version number" f"release a version with a conforming version number"
), ),
issue=12063, issue=12063,
gone_in="23.3", gone_in="24.0",
) )
for dep in package_details.dependencies: for dep in package_details.dependencies:
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier): if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
@ -183,5 +183,5 @@ def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
f"release a version with a conforming dependency specifiers" f"release a version with a conforming dependency specifiers"
), ),
issue=12063, issue=12063,
gone_in="23.3", gone_in="24.0",
) )

View File

@ -164,16 +164,14 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
for parent_dir, dir_scripts in warn_for.items(): 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: if len(sorted_scripts) == 1:
start_text = "script {} is".format(sorted_scripts[0]) start_text = f"script {sorted_scripts[0]} is"
else: else:
start_text = "scripts {} are".format( start_text = "scripts {} are".format(
", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1] ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1]
) )
msg_lines.append( msg_lines.append(
"The {} installed in '{}' which is not on PATH.".format( f"The {start_text} installed in '{parent_dir}' which is not on PATH."
start_text, parent_dir
)
) )
last_line_fmt = ( last_line_fmt = (
@ -321,9 +319,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
scripts_to_generate.append("pip = " + pip_script) scripts_to_generate.append("pip = " + pip_script)
if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
scripts_to_generate.append( scripts_to_generate.append(f"pip{sys.version_info[0]} = {pip_script}")
"pip{} = {}".format(sys.version_info[0], pip_script)
)
scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}") scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
# Delete any other versioned pip entry points # Delete any other versioned pip entry points
@ -336,9 +332,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
scripts_to_generate.append("easy_install = " + easy_install_script) scripts_to_generate.append("easy_install = " + easy_install_script)
scripts_to_generate.append( scripts_to_generate.append(
"easy_install-{} = {}".format( f"easy_install-{get_major_minor_version()} = {easy_install_script}"
get_major_minor_version(), easy_install_script
)
) )
# Delete any other versioned easy_install entry points # Delete any other versioned easy_install entry points
easy_install_ep = [ easy_install_ep = [
@ -408,10 +402,10 @@ class ScriptFile:
class MissingCallableSuffix(InstallationError): class MissingCallableSuffix(InstallationError):
def __init__(self, entry_point: str) -> None: def __init__(self, entry_point: str) -> None:
super().__init__( super().__init__(
"Invalid script entry point: {} - A callable " f"Invalid script entry point: {entry_point} - A callable "
"suffix is required. Cf https://packaging.python.org/" "suffix is required. Cf https://packaging.python.org/"
"specifications/entry-points/#use-for-scripts for more " "specifications/entry-points/#use-for-scripts for more "
"information.".format(entry_point) "information."
) )
@ -712,7 +706,7 @@ def req_error_context(req_description: str) -> Generator[None, None, None]:
try: try:
yield yield
except InstallationError as e: except InstallationError as e:
message = "For req: {}. {}".format(req_description, e.args[0]) message = f"For req: {req_description}. {e.args[0]}"
raise InstallationError(message) from e raise InstallationError(message) from e

View File

@ -47,6 +47,7 @@ from pip._internal.utils.misc import (
display_path, display_path,
hash_file, hash_file,
hide_url, hide_url,
redact_auth_from_requirement,
) )
from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.unpacking import unpack_file from pip._internal.utils.unpacking import unpack_file
@ -277,7 +278,7 @@ class RequirementPreparer:
information = str(display_path(req.link.file_path)) information = str(display_path(req.link.file_path))
else: else:
message = "Collecting %s" message = "Collecting %s"
information = str(req.req or req) information = redact_auth_from_requirement(req.req) if req.req else str(req)
# If we used req.req, inject requirement source if available (this # If we used req.req, inject requirement source if available (this
# would already be included if we used req directly) # would already be included if we used req directly)
@ -602,8 +603,8 @@ class RequirementPreparer:
) )
except NetworkConnectionError as exc: except NetworkConnectionError as exc:
raise InstallationError( raise InstallationError(
"Could not install requirement {} because of HTTP " f"Could not install requirement {req} because of HTTP "
"error {} for URL {}".format(req, exc, link) f"error {exc} for URL {link}"
) )
else: else:
file_path = self._downloaded[link.url] file_path = self._downloaded[link.url]
@ -683,9 +684,9 @@ class RequirementPreparer:
with indent_log(): with indent_log():
if self.require_hashes: if self.require_hashes:
raise InstallationError( raise InstallationError(
"The editable requirement {} cannot be installed when " f"The editable requirement {req} cannot be installed when "
"requiring hashes, because there is no single file to " "requiring hashes, because there is no single file to "
"hash.".format(req) "hash."
) )
req.ensure_has_source_dir(self.src_dir) req.ensure_has_source_dir(self.src_dir)
req.update_editable() req.update_editable()
@ -713,7 +714,7 @@ class RequirementPreparer:
assert req.satisfied_by, "req should have been satisfied but isn't" assert req.satisfied_by, "req should have been satisfied but isn't"
assert skip_reason is not None, ( assert skip_reason is not None, (
"did not get skip reason skipped but req.satisfied_by " "did not get skip reason skipped but req.satisfied_by "
"is set to {}".format(req.satisfied_by) f"is set to {req.satisfied_by}"
) )
logger.info( logger.info(
"Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version

View File

@ -8,10 +8,11 @@ These are meant to be used elsewhere within pip to create instances of
InstallRequirement. InstallRequirement.
""" """
import copy
import logging import logging
import os import os
import re import re
from typing import Dict, List, Optional, Set, Tuple, Union from typing import Collection, Dict, List, Optional, Set, Tuple, Union
from pip._vendor.packaging.markers import Marker from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
@ -57,6 +58,31 @@ def convert_extras(extras: Optional[str]) -> Set[str]:
return get_requirement("placeholder" + extras.lower()).extras return get_requirement("placeholder" + extras.lower()).extras
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(
# see https://peps.python.org/pep-0508/#complete-grammar
r"([\w\t .-]+)(\[[^\]]*\])?(.*)",
str(req),
flags=re.ASCII,
)
# ireq.req is a valid requirement so the regex should always match
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)
assert (
pre is not None and post is not None
), f"regex group selection for requirement {req} failed, this should never happen"
extras: str = "[%s]" % ",".join(sorted(new_extras)) if new_extras else ""
return Requirement(f"{pre}{extras}{post}")
def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]: def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
"""Parses an editable requirement into: """Parses an editable requirement into:
- a requirement name - a requirement name
@ -436,7 +462,7 @@ def install_req_from_req_string(
raise InstallationError( raise InstallationError(
"Packages installed from PyPI cannot depend on packages " "Packages installed from PyPI cannot depend on packages "
"which are not also hosted on PyPI.\n" "which are not also hosted on PyPI.\n"
"{} depends on {} ".format(comes_from.name, req) f"{comes_from.name} depends on {req} "
) )
return InstallRequirement( return InstallRequirement(
@ -504,3 +530,47 @@ def install_req_from_link_and_ireq(
config_settings=ireq.config_settings, config_settings=ireq.config_settings,
user_supplied=ireq.user_supplied, user_supplied=ireq.user_supplied,
) )
def install_req_drop_extras(ireq: InstallRequirement) -> InstallRequirement:
"""
Creates a new InstallationRequirement using the given template but without
any extras. Sets the original requirement as the new one's parent
(comes_from).
"""
return InstallRequirement(
req=(
_set_requirement_extras(ireq.req, set()) if ireq.req is not None else None
),
comes_from=ireq,
editable=ireq.editable,
link=ireq.link,
markers=ireq.markers,
use_pep517=ireq.use_pep517,
isolated=ireq.isolated,
global_options=ireq.global_options,
hash_options=ireq.hash_options,
constraint=ireq.constraint,
extras=[],
config_settings=ireq.config_settings,
user_supplied=ireq.user_supplied,
permit_editable_wheels=ireq.permit_editable_wheels,
)
def install_req_extend_extras(
ireq: InstallRequirement,
extras: Collection[str],
) -> InstallRequirement:
"""
Returns a copy of an installation requirement with some additional extras.
Makes a shallow copy of the ireq object.
"""
result = copy.copy(ireq)
result.extras = {*ireq.extras, *extras}
result.req = (
_set_requirement_extras(ireq.req, result.extras)
if ireq.req is not None
else None
)
return result

View File

@ -49,6 +49,7 @@ from pip._internal.utils.misc import (
display_path, display_path,
hide_url, hide_url,
is_installable_dir, is_installable_dir,
redact_auth_from_requirement,
redact_auth_from_url, redact_auth_from_url,
) )
from pip._internal.utils.packaging import safe_extra from pip._internal.utils.packaging import safe_extra
@ -191,9 +192,9 @@ class InstallRequirement:
def __str__(self) -> str: def __str__(self) -> str:
if self.req: if self.req:
s = str(self.req) s = redact_auth_from_requirement(self.req)
if self.link: if self.link:
s += " from {}".format(redact_auth_from_url(self.link.url)) s += f" from {redact_auth_from_url(self.link.url)}"
elif self.link: elif self.link:
s = redact_auth_from_url(self.link.url) s = redact_auth_from_url(self.link.url)
else: else:
@ -223,7 +224,7 @@ class InstallRequirement:
attributes = vars(self) attributes = vars(self)
names = sorted(attributes) names = sorted(attributes)
state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names)) state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names))
return "<{name} object: {{{state}}}>".format( return "<{name} object: {{{state}}}>".format(
name=self.__class__.__name__, name=self.__class__.__name__,
state=", ".join(state), state=", ".join(state),
@ -517,7 +518,7 @@ class InstallRequirement:
"to use --use-pep517 or add a " "to use --use-pep517 or add a "
"pyproject.toml file to the project" "pyproject.toml file to the project"
), ),
gone_in="23.3", gone_in="24.0",
) )
self.use_pep517 = False self.use_pep517 = False
return return
@ -756,8 +757,8 @@ class InstallRequirement:
if os.path.exists(archive_path): if os.path.exists(archive_path):
response = ask_path_exists( response = ask_path_exists(
"The file {} exists. (i)gnore, (w)ipe, " f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, "
"(b)ackup, (a)bort ".format(display_path(archive_path)), "(b)ackup, (a)bort ",
("i", "w", "b", "a"), ("i", "w", "b", "a"),
) )
if response == "i": if response == "i":
@ -907,7 +908,7 @@ def check_legacy_setup_py_options(
reason="--build-option and --global-option are deprecated.", reason="--build-option and --global-option are deprecated.",
issue=11859, issue=11859,
replacement="to use --config-settings", replacement="to use --config-settings",
gone_in="23.3", gone_in="24.0",
) )
logger.warning( logger.warning(
"Implying --no-binary=:all: due to the presence of " "Implying --no-binary=:all: due to the presence of "

View File

@ -99,7 +99,7 @@ class RequirementSet:
"or contact the package author to fix the version number" "or contact the package author to fix the version number"
), ),
issue=12063, issue=12063,
gone_in="23.3", gone_in="24.0",
) )
for dep in req.get_dist().iter_dependencies(): for dep in req.get_dist().iter_dependencies():
if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier): if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
@ -115,5 +115,5 @@ class RequirementSet:
"or contact the package author to fix the version number" "or contact the package author to fix the version number"
), ),
issue=12063, issue=12063,
gone_in="23.3", gone_in="24.0",
) )

View File

@ -71,16 +71,16 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
entries = dist.iter_declared_entries() entries = dist.iter_declared_entries()
if entries is None: if entries is None:
msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist) msg = f"Cannot uninstall {dist}, RECORD file not found."
installer = dist.installer installer = dist.installer
if not installer or installer == "pip": if not installer or installer == "pip":
dep = "{}=={}".format(dist.raw_name, dist.version) dep = f"{dist.raw_name}=={dist.version}"
msg += ( msg += (
" You might be able to recover from this via: " " You might be able to recover from this via: "
"'pip install --force-reinstall --no-deps {}'.".format(dep) f"'pip install --force-reinstall --no-deps {dep}'."
) )
else: else:
msg += " Hint: The package was installed by {}.".format(installer) msg += f" Hint: The package was installed by {installer}."
raise UninstallationError(msg) raise UninstallationError(msg)
for entry in entries: for entry in entries:

View File

@ -231,9 +231,7 @@ class Resolver(BaseResolver):
tags = compatibility_tags.get_supported() tags = compatibility_tags.get_supported()
if requirement_set.check_supported_wheels and not wheel.supported(tags): if requirement_set.check_supported_wheels and not wheel.supported(tags):
raise InstallationError( raise InstallationError(
"{} is not a supported wheel on this platform.".format( f"{wheel.filename} is not a supported wheel on this platform."
wheel.filename
)
) )
# This next bit is really a sanity check. # This next bit is really a sanity check.
@ -287,9 +285,9 @@ class Resolver(BaseResolver):
) )
if does_not_satisfy_constraint: if does_not_satisfy_constraint:
raise InstallationError( raise InstallationError(
"Could not satisfy constraints for '{}': " f"Could not satisfy constraints for '{install_req.name}': "
"installation from path or url cannot be " "installation from path or url cannot be "
"constrained to a version".format(install_req.name) "constrained to a version"
) )
# If we're now installing a constraint, mark the existing # If we're now installing a constraint, mark the existing
# object for real installation. # object for real installation.
@ -398,9 +396,9 @@ class Resolver(BaseResolver):
# "UnicodeEncodeError: 'ascii' codec can't encode character" # "UnicodeEncodeError: 'ascii' codec can't encode character"
# in Python 2 when the reason contains non-ascii characters. # in Python 2 when the reason contains non-ascii characters.
"The candidate selected for download or install is a " "The candidate selected for download or install is a "
"yanked version: {candidate}\n" f"yanked version: {best_candidate}\n"
"Reason for being yanked: {reason}" f"Reason for being yanked: {reason}"
).format(candidate=best_candidate, reason=reason) )
logger.warning(msg) logger.warning(msg)
return link return link

View File

@ -159,10 +159,7 @@ class _InstallRequirementBackedCandidate(Candidate):
return f"{self.name} {self.version}" return f"{self.name} {self.version}"
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}({link!r})".format( return f"{self.__class__.__name__}({str(self._link)!r})"
class_name=self.__class__.__name__,
link=str(self._link),
)
def __hash__(self) -> int: def __hash__(self) -> int:
return hash((self.__class__, self._link)) return hash((self.__class__, self._link))
@ -240,7 +237,7 @@ class _InstallRequirementBackedCandidate(Candidate):
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
requires = self.dist.iter_dependencies() if with_requires else () requires = self.dist.iter_dependencies() if with_requires else ()
for r in requires: for r in requires:
yield self._factory.make_requirement_from_spec(str(r), self._ireq) yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
yield self._factory.make_requires_python_requirement(self.dist.requires_python) yield self._factory.make_requires_python_requirement(self.dist.requires_python)
def get_install_requirement(self) -> Optional[InstallRequirement]: def get_install_requirement(self) -> Optional[InstallRequirement]:
@ -354,10 +351,7 @@ class AlreadyInstalledCandidate(Candidate):
return str(self.dist) return str(self.dist)
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}({distribution!r})".format( return f"{self.__class__.__name__}({self.dist!r})"
class_name=self.__class__.__name__,
distribution=self.dist,
)
def __hash__(self) -> int: def __hash__(self) -> int:
return hash((self.__class__, self.name, self.version)) return hash((self.__class__, self.name, self.version))
@ -392,7 +386,7 @@ class AlreadyInstalledCandidate(Candidate):
if not with_requires: if not with_requires:
return return
for r in self.dist.iter_dependencies(): for r in self.dist.iter_dependencies():
yield self._factory.make_requirement_from_spec(str(r), self._ireq) yield from self._factory.make_requirements_from_spec(str(r), self._ireq)
def get_install_requirement(self) -> Optional[InstallRequirement]: def get_install_requirement(self) -> Optional[InstallRequirement]:
return None return None
@ -427,7 +421,17 @@ class ExtrasCandidate(Candidate):
self, self,
base: BaseCandidate, base: BaseCandidate,
extras: FrozenSet[str], extras: FrozenSet[str],
*,
comes_from: Optional[InstallRequirement] = None,
) -> None: ) -> None:
"""
:param comes_from: the InstallRequirement that led to this candidate if it
differs from the base's InstallRequirement. This will often be the
case in the sense that this candidate's requirement has the extras
while the base's does not. Unlike the InstallRequirement backed
candidates, this requirement is used solely for reporting purposes,
it does not do any leg work.
"""
self.base = base self.base = base
self.extras = frozenset(canonicalize_name(e) for e in extras) self.extras = frozenset(canonicalize_name(e) for e in extras)
# If any extras are requested in their non-normalized forms, keep track # If any extras are requested in their non-normalized forms, keep track
@ -438,17 +442,14 @@ class ExtrasCandidate(Candidate):
# TODO: Remove this attribute when packaging is upgraded to support the # TODO: Remove this attribute when packaging is upgraded to support the
# marker comparison logic specified in PEP 685. # marker comparison logic specified in PEP 685.
self._unnormalized_extras = extras.difference(self.extras) self._unnormalized_extras = extras.difference(self.extras)
self._comes_from = comes_from if comes_from is not None else self.base._ireq
def __str__(self) -> str: def __str__(self) -> str:
name, rest = str(self.base).split(" ", 1) name, rest = str(self.base).split(" ", 1)
return "{}[{}] {}".format(name, ",".join(self.extras), rest) return "{}[{}] {}".format(name, ",".join(self.extras), rest)
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}(base={base!r}, extras={extras!r})".format( return f"{self.__class__.__name__}(base={self.base!r}, extras={self.extras!r})"
class_name=self.__class__.__name__,
base=self.base,
extras=self.extras,
)
def __hash__(self) -> int: def __hash__(self) -> int:
return hash((self.base, self.extras)) return hash((self.base, self.extras))
@ -543,11 +544,11 @@ class ExtrasCandidate(Candidate):
valid_extras = self._calculate_valid_requested_extras() valid_extras = self._calculate_valid_requested_extras()
for r in self.base.dist.iter_dependencies(valid_extras): for r in self.base.dist.iter_dependencies(valid_extras):
requirement = factory.make_requirement_from_spec( yield from factory.make_requirements_from_spec(
str(r), self.base._ireq, valid_extras str(r),
self._comes_from,
valid_extras,
) )
if requirement:
yield requirement
def get_install_requirement(self) -> Optional[InstallRequirement]: def get_install_requirement(self) -> Optional[InstallRequirement]:
# We don't return anything here, because we always # We don't return anything here, because we always

View File

@ -62,6 +62,7 @@ from .requirements import (
ExplicitRequirement, ExplicitRequirement,
RequiresPythonRequirement, RequiresPythonRequirement,
SpecifierRequirement, SpecifierRequirement,
SpecifierWithoutExtrasRequirement,
UnsatisfiableRequirement, UnsatisfiableRequirement,
) )
@ -141,12 +142,14 @@ class Factory:
self, self,
base: BaseCandidate, base: BaseCandidate,
extras: FrozenSet[str], extras: FrozenSet[str],
*,
comes_from: Optional[InstallRequirement] = None,
) -> ExtrasCandidate: ) -> ExtrasCandidate:
cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras)) cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras))
try: try:
candidate = self._extras_candidate_cache[cache_key] candidate = self._extras_candidate_cache[cache_key]
except KeyError: except KeyError:
candidate = ExtrasCandidate(base, extras) candidate = ExtrasCandidate(base, extras, comes_from=comes_from)
self._extras_candidate_cache[cache_key] = candidate self._extras_candidate_cache[cache_key] = candidate
return candidate return candidate
@ -163,7 +166,7 @@ class Factory:
self._installed_candidate_cache[dist.canonical_name] = base self._installed_candidate_cache[dist.canonical_name] = base
if not extras: if not extras:
return base return base
return self._make_extras_candidate(base, extras) return self._make_extras_candidate(base, extras, comes_from=template)
def _make_candidate_from_link( def _make_candidate_from_link(
self, self,
@ -225,7 +228,7 @@ class Factory:
if not extras: if not extras:
return base return base
return self._make_extras_candidate(base, extras) return self._make_extras_candidate(base, extras, comes_from=template)
def _iter_found_candidates( def _iter_found_candidates(
self, self,
@ -387,16 +390,21 @@ class Factory:
if ireq is not None: if ireq is not None:
ireqs.append(ireq) ireqs.append(ireq)
# If the current identifier contains extras, add explicit candidates # If the current identifier contains extras, add requires and explicit
# from entries from extra-less identifier. # candidates from entries from extra-less identifier.
with contextlib.suppress(InvalidRequirement): with contextlib.suppress(InvalidRequirement):
parsed_requirement = get_requirement(identifier) parsed_requirement = get_requirement(identifier)
explicit_candidates.update( if parsed_requirement.name != identifier:
self._iter_explicit_candidates_from_base( explicit_candidates.update(
requirements.get(parsed_requirement.name, ()), self._iter_explicit_candidates_from_base(
frozenset(parsed_requirement.extras), requirements.get(parsed_requirement.name, ()),
), frozenset(parsed_requirement.extras),
) ),
)
for req in requirements.get(parsed_requirement.name, []):
_, ireq = req.get_candidate_lookup()
if ireq is not None:
ireqs.append(ireq)
# Add explicit candidates from constraints. We only do this if there are # Add explicit candidates from constraints. We only do this if there are
# known ireqs, which represent requirements not already explicit. If # known ireqs, which represent requirements not already explicit. If
@ -439,37 +447,49 @@ class Factory:
and all(req.is_satisfied_by(c) for req in requirements[identifier]) and all(req.is_satisfied_by(c) for req in requirements[identifier])
) )
def _make_requirement_from_install_req( def _make_requirements_from_install_req(
self, ireq: InstallRequirement, requested_extras: Iterable[str] self, ireq: InstallRequirement, requested_extras: Iterable[str]
) -> Optional[Requirement]: ) -> Iterator[Requirement]:
"""
Returns requirement objects associated with the given InstallRequirement. In
most cases this will be a single object but the following special cases exist:
- the InstallRequirement has markers that do not apply -> result is empty
- the InstallRequirement has both a constraint and extras -> result is split
in two requirement objects: one with the constraint and one with the
extra. This allows centralized constraint handling for the base,
resulting in fewer candidate rejections.
"""
if not ireq.match_markers(requested_extras): if not ireq.match_markers(requested_extras):
logger.info( logger.info(
"Ignoring %s: markers '%s' don't match your environment", "Ignoring %s: markers '%s' don't match your environment",
ireq.name, ireq.name,
ireq.markers, ireq.markers,
) )
return None elif not ireq.link:
if not ireq.link: if ireq.extras and ireq.req is not None and ireq.req.specifier:
return SpecifierRequirement(ireq) yield SpecifierWithoutExtrasRequirement(ireq)
self._fail_if_link_is_unsupported_wheel(ireq.link) yield SpecifierRequirement(ireq)
cand = self._make_candidate_from_link( else:
ireq.link, self._fail_if_link_is_unsupported_wheel(ireq.link)
extras=frozenset(ireq.extras), cand = self._make_candidate_from_link(
template=ireq, ireq.link,
name=canonicalize_name(ireq.name) if ireq.name else None, extras=frozenset(ireq.extras),
version=None, template=ireq,
) name=canonicalize_name(ireq.name) if ireq.name else None,
if cand is None: version=None,
# There's no way we can satisfy a URL requirement if the underlying )
# candidate fails to build. An unnamed URL must be user-supplied, so if cand is None:
# we fail eagerly. If the URL is named, an unsatisfiable requirement # There's no way we can satisfy a URL requirement if the underlying
# can make the resolver do the right thing, either backtrack (and # candidate fails to build. An unnamed URL must be user-supplied, so
# maybe find some other requirement that's buildable) or raise a # we fail eagerly. If the URL is named, an unsatisfiable requirement
# ResolutionImpossible eventually. # can make the resolver do the right thing, either backtrack (and
if not ireq.name: # maybe find some other requirement that's buildable) or raise a
raise self._build_failures[ireq.link] # ResolutionImpossible eventually.
return UnsatisfiableRequirement(canonicalize_name(ireq.name)) if not ireq.name:
return self.make_requirement_from_candidate(cand) raise self._build_failures[ireq.link]
yield UnsatisfiableRequirement(canonicalize_name(ireq.name))
else:
yield self.make_requirement_from_candidate(cand)
def collect_root_requirements( def collect_root_requirements(
self, root_ireqs: List[InstallRequirement] self, root_ireqs: List[InstallRequirement]
@ -490,15 +510,27 @@ class Factory:
else: else:
collected.constraints[name] = Constraint.from_ireq(ireq) collected.constraints[name] = Constraint.from_ireq(ireq)
else: else:
req = self._make_requirement_from_install_req( reqs = list(
ireq, self._make_requirements_from_install_req(
requested_extras=(), ireq,
requested_extras=(),
)
) )
if req is None: if not reqs:
continue continue
if ireq.user_supplied and req.name not in collected.user_requested: template = reqs[0]
collected.user_requested[req.name] = i if ireq.user_supplied and template.name not in collected.user_requested:
collected.requirements.append(req) collected.user_requested[template.name] = i
collected.requirements.extend(reqs)
# Put requirements with extras at the end of the root requires. This does not
# affect resolvelib's picking preference but it does affect its initial criteria
# population: by putting extras at the end we enable the candidate finder to
# present resolvelib with a smaller set of candidates to resolvelib, already
# taking into account any non-transient constraints on the associated base. This
# means resolvelib will have fewer candidates to visit and reject.
# Python's list sort is stable, meaning relative order is kept for objects with
# the same key.
collected.requirements.sort(key=lambda r: r.name != r.project_name)
return collected return collected
def make_requirement_from_candidate( def make_requirement_from_candidate(
@ -506,14 +538,23 @@ class Factory:
) -> ExplicitRequirement: ) -> ExplicitRequirement:
return ExplicitRequirement(candidate) return ExplicitRequirement(candidate)
def make_requirement_from_spec( def make_requirements_from_spec(
self, self,
specifier: str, specifier: str,
comes_from: Optional[InstallRequirement], comes_from: Optional[InstallRequirement],
requested_extras: Iterable[str] = (), requested_extras: Iterable[str] = (),
) -> Optional[Requirement]: ) -> Iterator[Requirement]:
"""
Returns requirement objects associated with the given specifier. In most cases
this will be a single object but the following special cases exist:
- the specifier has markers that do not apply -> result is empty
- the specifier has both a constraint and extras -> result is split
in two requirement objects: one with the constraint and one with the
extra. This allows centralized constraint handling for the base,
resulting in fewer candidate rejections.
"""
ireq = self._make_install_req_from_spec(specifier, comes_from) ireq = self._make_install_req_from_spec(specifier, comes_from)
return self._make_requirement_from_install_req(ireq, requested_extras) return self._make_requirements_from_install_req(ireq, requested_extras)
def make_requires_python_requirement( def make_requires_python_requirement(
self, self,
@ -712,8 +753,8 @@ class Factory:
info = "the requested packages" info = "the requested packages"
msg = ( msg = (
"Cannot install {} because these package versions " f"Cannot install {info} because these package versions "
"have conflicting dependencies.".format(info) "have conflicting dependencies."
) )
logger.critical(msg) logger.critical(msg)
msg = "\nThe conflict is caused by:" msg = "\nThe conflict is caused by:"

View File

@ -1,6 +1,7 @@
from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._internal.req.constructors import install_req_drop_extras
from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_install import InstallRequirement
from .base import Candidate, CandidateLookup, Requirement, format_name from .base import Candidate, CandidateLookup, Requirement, format_name
@ -14,10 +15,7 @@ class ExplicitRequirement(Requirement):
return str(self.candidate) return str(self.candidate)
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}({candidate!r})".format( return f"{self.__class__.__name__}({self.candidate!r})"
class_name=self.__class__.__name__,
candidate=self.candidate,
)
@property @property
def project_name(self) -> NormalizedName: def project_name(self) -> NormalizedName:
@ -43,16 +41,13 @@ class SpecifierRequirement(Requirement):
def __init__(self, ireq: InstallRequirement) -> None: def __init__(self, ireq: InstallRequirement) -> None:
assert ireq.link is None, "This is a link, not a specifier" assert ireq.link is None, "This is a link, not a specifier"
self._ireq = ireq self._ireq = ireq
self._extras = frozenset(canonicalize_name(e) for e in ireq.extras) self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
def __str__(self) -> str: def __str__(self) -> str:
return str(self._ireq.req) return str(self._ireq.req)
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}({requirement!r})".format( return f"{self.__class__.__name__}({str(self._ireq.req)!r})"
class_name=self.__class__.__name__,
requirement=str(self._ireq.req),
)
@property @property
def project_name(self) -> NormalizedName: def project_name(self) -> NormalizedName:
@ -92,6 +87,18 @@ class SpecifierRequirement(Requirement):
return spec.contains(candidate.version, prereleases=True) return spec.contains(candidate.version, prereleases=True)
class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
"""
Requirement backed by an install requirement on a base package.
Trims extras from its install requirement if there are any.
"""
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._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
class RequiresPythonRequirement(Requirement): class RequiresPythonRequirement(Requirement):
"""A requirement representing Requires-Python metadata.""" """A requirement representing Requires-Python metadata."""
@ -103,10 +110,7 @@ class RequiresPythonRequirement(Requirement):
return f"Python {self.specifier}" return f"Python {self.specifier}"
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}({specifier!r})".format( return f"{self.__class__.__name__}({str(self.specifier)!r})"
class_name=self.__class__.__name__,
specifier=str(self.specifier),
)
@property @property
def project_name(self) -> NormalizedName: def project_name(self) -> NormalizedName:
@ -142,10 +146,7 @@ class UnsatisfiableRequirement(Requirement):
return f"{self._name} (unavailable)" return f"{self._name} (unavailable)"
def __repr__(self) -> str: def __repr__(self) -> str:
return "{class_name}({name!r})".format( return f"{self.__class__.__name__}({str(self._name)!r})"
class_name=self.__class__.__name__,
name=str(self._name),
)
@property @property
def project_name(self) -> NormalizedName: def project_name(self) -> NormalizedName:

View File

@ -1,3 +1,4 @@
import contextlib
import functools import functools
import logging import logging
import os import os
@ -11,6 +12,7 @@ from pip._vendor.resolvelib.structs import DirectedGraph
from pip._internal.cache import WheelCache from pip._internal.cache import WheelCache
from pip._internal.index.package_finder import PackageFinder from pip._internal.index.package_finder import PackageFinder
from pip._internal.operations.prepare import RequirementPreparer from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.constructors import install_req_extend_extras
from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.req_set import RequirementSet from pip._internal.req.req_set import RequirementSet
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
@ -19,6 +21,7 @@ from pip._internal.resolution.resolvelib.reporter import (
PipDebuggingReporter, PipDebuggingReporter,
PipReporter, PipReporter,
) )
from pip._internal.utils.packaging import get_requirement
from .base import Candidate, Requirement from .base import Candidate, Requirement
from .factory import Factory from .factory import Factory
@ -101,9 +104,24 @@ class Resolver(BaseResolver):
raise error from e raise error from e
req_set = RequirementSet(check_supported_wheels=check_supported_wheels) req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
for candidate in result.mapping.values(): # process candidates with extras last to ensure their base equivalent is
# already in the req_set if appropriate.
# Python's sort is stable so using a binary key function keeps relative order
# within both subsets.
for candidate in sorted(
result.mapping.values(), key=lambda c: c.name != c.project_name
):
ireq = candidate.get_install_requirement() ireq = candidate.get_install_requirement()
if ireq is None: if ireq is None:
if candidate.name != candidate.project_name:
# extend existing req's extras
with contextlib.suppress(KeyError):
req = req_set.get_requirement(candidate.project_name)
req_set.add_named_requirement(
install_req_extend_extras(
req, get_requirement(candidate.name).extras
)
)
continue continue
# Check if there is already an installation under the same name, # Check if there is already an installation under the same name,

View File

@ -39,6 +39,15 @@ def _get_statefile_name(key: str) -> str:
return name return name
def _convert_date(isodate: str) -> datetime.datetime:
"""Convert an ISO format string to a date.
Handles the format 2020-01-22T14:24:01Z (trailing Z)
which is not supported by older versions of fromisoformat.
"""
return datetime.datetime.fromisoformat(isodate.replace("Z", "+00:00"))
class SelfCheckState: class SelfCheckState:
def __init__(self, cache_dir: str) -> None: def __init__(self, cache_dir: str) -> None:
self._state: Dict[str, Any] = {} self._state: Dict[str, Any] = {}
@ -73,7 +82,7 @@ class SelfCheckState:
return None return None
# Determine if we need to refresh the state # Determine if we need to refresh the state
last_check = datetime.datetime.fromisoformat(self._state["last_check"]) last_check = _convert_date(self._state["last_check"])
time_since_last_check = current_time - last_check time_since_last_check = current_time - last_check
if time_since_last_check > _WEEK: if time_since_last_check > _WEEK:
return None return None
@ -233,7 +242,7 @@ def pip_self_version_check(session: PipSession, options: optparse.Values) -> Non
), ),
) )
if upgrade_prompt is not None: if upgrade_prompt is not None:
logger.warning("[present-rich] %s", upgrade_prompt) logger.warning("%s", upgrade_prompt, extra={"rich": True})
except Exception: except Exception:
logger.warning("There was an error checking the latest version of pip.") logger.warning("There was an error checking the latest version of pip.")
logger.debug("See below for error", exc_info=True) logger.debug("See below for error", exc_info=True)

View File

@ -155,8 +155,8 @@ class RichPipStreamHandler(RichHandler):
# If we are given a diagnostic error to present, present it with indentation. # If we are given a diagnostic error to present, present it with indentation.
assert isinstance(record.args, tuple) assert isinstance(record.args, tuple)
if record.msg == "[present-rich] %s" and len(record.args) == 1: if getattr(record, "rich", False):
rich_renderable = record.args[0] (rich_renderable,) = record.args
assert isinstance( assert isinstance(
rich_renderable, (ConsoleRenderable, RichCast, str) rich_renderable, (ConsoleRenderable, RichCast, str)
), f"{rich_renderable} is not rich-console-renderable" ), f"{rich_renderable} is not rich-console-renderable"

View File

@ -35,6 +35,7 @@ from typing import (
cast, cast,
) )
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.pyproject_hooks import BuildBackendHookCaller from pip._vendor.pyproject_hooks import BuildBackendHookCaller
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
@ -76,11 +77,7 @@ def get_pip_version() -> str:
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
pip_pkg_dir = os.path.abspath(pip_pkg_dir) pip_pkg_dir = os.path.abspath(pip_pkg_dir)
return "pip {} from {} (python {})".format( return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})"
__version__,
pip_pkg_dir,
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]:
@ -144,9 +141,9 @@ def rmtree(
) )
if sys.version_info >= (3, 12): if sys.version_info >= (3, 12):
# See https://docs.python.org/3.12/whatsnew/3.12.html#shutil. # See https://docs.python.org/3.12/whatsnew/3.12.html#shutil.
shutil.rmtree(dir, onexc=handler) shutil.rmtree(dir, onexc=handler) # type: ignore
else: else:
shutil.rmtree(dir, onerror=handler) shutil.rmtree(dir, onerror=handler) # type: ignore
def _onerror_ignore(*_args: Any) -> None: def _onerror_ignore(*_args: Any) -> None:
@ -278,13 +275,13 @@ def strtobool(val: str) -> int:
def format_size(bytes: float) -> str: def format_size(bytes: float) -> str:
if bytes > 1000 * 1000: if bytes > 1000 * 1000:
return "{:.1f} MB".format(bytes / 1000.0 / 1000) return f"{bytes / 1000.0 / 1000:.1f} MB"
elif bytes > 10 * 1000: elif bytes > 10 * 1000:
return "{} kB".format(int(bytes / 1000)) return f"{int(bytes / 1000)} kB"
elif bytes > 1000: elif bytes > 1000:
return "{:.1f} kB".format(bytes / 1000.0) return f"{bytes / 1000.0:.1f} kB"
else: else:
return "{} bytes".format(int(bytes)) 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]]:
@ -521,9 +518,7 @@ def redact_netloc(netloc: str) -> str:
else: else:
user = urllib.parse.quote(user) user = urllib.parse.quote(user)
password = ":****" password = ":****"
return "{user}{password}@{netloc}".format( return f"{user}{password}@{netloc}"
user=user, password=password, netloc=netloc
)
def _transform_url( def _transform_url(
@ -578,13 +573,20 @@ def redact_auth_from_url(url: str) -> str:
return _transform_url(url, _redact_netloc)[0] return _transform_url(url, _redact_netloc)[0]
def redact_auth_from_requirement(req: Requirement) -> str:
"""Replace the password in a given requirement url with ****."""
if not req.url:
return str(req)
return str(req).replace(req.url, redact_auth_from_url(req.url))
class HiddenText: class HiddenText:
def __init__(self, secret: str, redacted: str) -> None: def __init__(self, secret: str, redacted: str) -> None:
self.secret = secret self.secret = secret
self.redacted = redacted self.redacted = redacted
def __repr__(self) -> str: def __repr__(self) -> str:
return "<HiddenText {!r}>".format(str(self)) return f"<HiddenText {str(self)!r}>"
def __str__(self) -> str: def __str__(self) -> str:
return self.redacted return self.redacted

View File

@ -215,7 +215,7 @@ def call_subprocess(
output_lines=all_output if not showing_subprocess else None, output_lines=all_output if not showing_subprocess else None,
) )
if log_failed_cmd: if log_failed_cmd:
subprocess_logger.error("[present-rich] %s", error) subprocess_logger.error("%s", error, extra={"rich": True})
subprocess_logger.verbose( subprocess_logger.verbose(
"[bold magenta]full command[/]: [blue]%s[/]", "[bold magenta]full command[/]: [blue]%s[/]",
escape(format_command_args(cmd)), escape(format_command_args(cmd)),

View File

@ -28,7 +28,7 @@ def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
metadata = wheel_metadata(wheel_zip, info_dir) metadata = wheel_metadata(wheel_zip, info_dir)
version = wheel_version(metadata) version = wheel_version(metadata)
except UnsupportedWheel as e: except UnsupportedWheel as e:
raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e))) raise UnsupportedWheel(f"{name} has an invalid wheel, {str(e)}")
check_compatibility(version, name) check_compatibility(version, name)
@ -60,9 +60,7 @@ def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
canonical_name = canonicalize_name(name) canonical_name = canonicalize_name(name)
if not info_dir_name.startswith(canonical_name): if not info_dir_name.startswith(canonical_name):
raise UnsupportedWheel( raise UnsupportedWheel(
".dist-info directory {!r} does not start with {!r}".format( f".dist-info directory {info_dir!r} does not start with {canonical_name!r}"
info_dir, canonical_name
)
) )
return info_dir return info_dir

View File

@ -101,7 +101,7 @@ class Git(VersionControl):
if not match: if not match:
logger.warning("Can't parse git version: %s", version) logger.warning("Can't parse git version: %s", version)
return () return ()
return tuple(int(c) for c in match.groups()) return (int(match.group(1)), int(match.group(2)))
@classmethod @classmethod
def get_current_branch(cls, location: str) -> Optional[str]: def get_current_branch(cls, location: str) -> Optional[str]:

View File

@ -31,7 +31,7 @@ class Mercurial(VersionControl):
@staticmethod @staticmethod
def get_base_rev_args(rev: str) -> List[str]: def get_base_rev_args(rev: str) -> List[str]:
return ["-r", rev] return [f"--rev={rev}"]
def fetch_new( def fetch_new(
self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int

View File

@ -405,9 +405,9 @@ class VersionControl:
scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
if "+" not in scheme: if "+" not in scheme:
raise ValueError( raise ValueError(
"Sorry, {!r} is a malformed VCS url. " f"Sorry, {url!r} is a malformed VCS url. "
"The format is <vcs>+<protocol>://<url>, " "The format is <vcs>+<protocol>://<url>, "
"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url) "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
) )
# Remove the vcs prefix. # Remove the vcs prefix.
scheme = scheme.split("+", 1)[1] scheme = scheme.split("+", 1)[1]
@ -417,9 +417,9 @@ class VersionControl:
path, rev = path.rsplit("@", 1) path, rev = path.rsplit("@", 1)
if not rev: if not rev:
raise InstallationError( raise InstallationError(
"The URL {!r} has an empty revision (after @) " f"The URL {url!r} has an empty revision (after @) "
"which is not supported. Include a revision after @ " "which is not supported. Include a revision after @ "
"or remove @ from the URL.".format(url) "or remove @ from the URL."
) )
url = urllib.parse.urlunsplit((scheme, netloc, path, query, "")) url = urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
return url, rev, user_pass return url, rev, user_pass
@ -566,7 +566,7 @@ class VersionControl:
self.name, self.name,
url, url,
) )
response = ask_path_exists("What to do? {}".format(prompt[0]), prompt[1]) response = ask_path_exists(f"What to do? {prompt[0]}", prompt[1])
if response == "a": if response == "a":
sys.exit(-1) sys.exit(-1)

View File

@ -140,15 +140,15 @@ def _verify_one(req: InstallRequirement, wheel_path: str) -> None:
w = Wheel(os.path.basename(wheel_path)) w = Wheel(os.path.basename(wheel_path))
if canonicalize_name(w.name) != canonical_name: if canonicalize_name(w.name) != canonical_name:
raise InvalidWheelFilename( raise InvalidWheelFilename(
"Wheel has unexpected file name: expected {!r}, " f"Wheel has unexpected file name: expected {canonical_name!r}, "
"got {!r}".format(canonical_name, w.name), f"got {w.name!r}",
) )
dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name) dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name)
dist_verstr = str(dist.version) dist_verstr = str(dist.version)
if canonicalize_version(dist_verstr) != canonicalize_version(w.version): if canonicalize_version(dist_verstr) != canonicalize_version(w.version):
raise InvalidWheelFilename( raise InvalidWheelFilename(
"Wheel has unexpected file name: expected {!r}, " f"Wheel has unexpected file name: expected {dist_verstr!r}, "
"got {!r}".format(dist_verstr, w.version), f"got {w.version!r}",
) )
metadata_version_value = dist.metadata_version metadata_version_value = dist.metadata_version
if metadata_version_value is None: if metadata_version_value is None:
@ -160,8 +160,7 @@ def _verify_one(req: InstallRequirement, wheel_path: str) -> None:
raise UnsupportedWheel(msg) raise UnsupportedWheel(msg)
if metadata_version >= Version("1.2") and not isinstance(dist.version, Version): if metadata_version >= Version("1.2") and not isinstance(dist.version, Version):
raise UnsupportedWheel( raise UnsupportedWheel(
"Metadata 1.2 mandates PEP 440 version, " f"Metadata 1.2 mandates PEP 440 version, but {dist_verstr!r} is not"
"but {!r} is not".format(dist_verstr)
) )

View File

@ -1,2 +1,2 @@
# This file is protected via CODEOWNERS # This file is protected via CODEOWNERS
__version__ = "1.26.16" __version__ = "1.26.17"

View File

@ -1,6 +1,9 @@
from __future__ import absolute_import from __future__ import absolute_import
import sys
from .filepost import encode_multipart_formdata from .filepost import encode_multipart_formdata
from .packages import six
from .packages.six.moves.urllib.parse import urlencode from .packages.six.moves.urllib.parse import urlencode
__all__ = ["RequestMethods"] __all__ = ["RequestMethods"]
@ -168,3 +171,21 @@ class RequestMethods(object):
extra_kw.update(urlopen_kw) extra_kw.update(urlopen_kw)
return self.urlopen(method, url, **extra_kw) return self.urlopen(method, url, **extra_kw)
if not six.PY2:
class RequestModule(sys.modules[__name__].__class__):
def __call__(self, *args, **kwargs):
"""
If user tries to call this module directly urllib3 v2.x style raise an error to the user
suggesting they may need urllib3 v2
"""
raise TypeError(
"'module' object is not callable\n"
"urllib3.request() method is not supported in this release, "
"upgrade to urllib3 v2 to use it\n"
"see https://urllib3.readthedocs.io/en/stable/v2-migration-guide.html"
)
sys.modules[__name__].__class__ = RequestModule

View File

@ -235,7 +235,7 @@ class Retry(object):
RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503])
#: Default headers to be used for ``remove_headers_on_redirect`` #: Default headers to be used for ``remove_headers_on_redirect``
DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"])
#: Maximum backoff time. #: Maximum backoff time.
DEFAULT_BACKOFF_MAX = 120 DEFAULT_BACKOFF_MAX = 120

View File

@ -11,7 +11,7 @@ requests==2.31.0
certifi==2023.7.22 certifi==2023.7.22
chardet==5.1.0 chardet==5.1.0
idna==3.4 idna==3.4
urllib3==1.26.16 urllib3==1.26.17
rich==13.4.2 rich==13.4.2
pygments==2.15.1 pygments==2.15.1
typing_extensions==4.7.1 typing_extensions==4.7.1

Some files were not shown because too many files have changed in this diff Show More