mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
Merge branch 'main' into per-req-config-settings
This commit is contained in:
commit
69cfd23c17
30
.github/chronographer.yml
vendored
Normal file
30
.github/chronographer.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
|
||||
action-hints:
|
||||
# check-title-prefix: chng # default: `{{ branch-protection-check-name }}: `
|
||||
external-docs-url: https://pip.pypa.io/how-to-changelog
|
||||
inline-markdown: >
|
||||
Check out https://pip.pypa.io/how-to-changelog
|
||||
|
||||
branch-protection-check-name: Timeline protection
|
||||
|
||||
enforce-name:
|
||||
# suffix: .md
|
||||
suffix: .rst
|
||||
|
||||
exclude:
|
||||
bots:
|
||||
- dependabot-preview
|
||||
- dependabot
|
||||
- patchback
|
||||
humans:
|
||||
- pyup-bot
|
||||
|
||||
labels:
|
||||
skip-changelog: skip news
|
||||
|
||||
paths: # relative modified file paths that do or don't need changelog mention
|
||||
exclude: []
|
||||
include: []
|
||||
|
||||
...
|
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -11,11 +11,6 @@ on:
|
|||
schedule:
|
||||
- cron: 0 0 * * MON # Run every Monday at 00:00 UTC
|
||||
|
||||
env:
|
||||
# The "FORCE_COLOR" variable, when set to 1,
|
||||
# tells Nox to colorize itself.
|
||||
FORCE_COLOR: "1"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
|
28
.github/workflows/update-rtd-redirects.yml
vendored
Normal file
28
.github/workflows/update-rtd-redirects.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: Update documentation redirects
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: 0 0 * * MON # Run every Monday at 00:00 UTC
|
||||
|
||||
env:
|
||||
FORCE_COLOR: "1"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update-rtd-redirects:
|
||||
runs-on: ubuntu-latest
|
||||
environment: RTD Deploys
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- run: pip install httpx pyyaml rich
|
||||
- run: python tools/update-rtd-redirects.py
|
||||
env:
|
||||
RTD_API_TOKEN: ${{ secrets.RTD_API_TOKEN }}
|
|
@ -33,7 +33,7 @@ repos:
|
|||
exclude: tests/data
|
||||
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
files: \.py$
|
||||
|
@ -47,11 +47,12 @@ repos:
|
|||
additional_dependencies: [
|
||||
'keyring==23.0.1',
|
||||
'nox==2021.6.12',
|
||||
'pytest==7.1.1',
|
||||
'pytest',
|
||||
'types-docutils==0.18.3',
|
||||
'types-setuptools==57.4.14',
|
||||
'types-freezegun==1.1.9',
|
||||
'types-six==1.16.15',
|
||||
'types-pyyaml==6.0.12.2',
|
||||
]
|
||||
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
|
|
15
.readthedocs-custom-redirects.yml
Normal file
15
.readthedocs-custom-redirects.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
# This file is read by tools/update-rtd-redirects.py.
|
||||
# It is related to Read the Docs, but is not a file processed by the platform.
|
||||
|
||||
/dev/news-entry-failure: >-
|
||||
https://pip.pypa.io/en/latest/development/contributing/#news-entries
|
||||
/errors/resolution-impossible: >-
|
||||
https://pip.pypa.io/en/stable/topics/dependency-resolution/#dealing-with-dependency-conflicts
|
||||
/surveys/backtracking: >-
|
||||
https://forms.gle/LkZP95S4CfqBAU1N6
|
||||
/warnings/backtracking: >-
|
||||
https://pip.pypa.io/en/stable/topics/dependency-resolution/#possible-ways-to-reduce-backtracking
|
||||
/warnings/enable-long-paths: >-
|
||||
https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later
|
||||
/warnings/venv: >-
|
||||
https://docs.python.org/3/tutorial/venv.html
|
16
AUTHORS.txt
16
AUTHORS.txt
|
@ -128,6 +128,7 @@ Chris Hunt
|
|||
Chris Jerdonek
|
||||
Chris McDonough
|
||||
Chris Pawley
|
||||
Chris Pryer
|
||||
Chris Wolfe
|
||||
Christian Clauss
|
||||
Christian Heimes
|
||||
|
@ -169,7 +170,9 @@ Daniel Jost
|
|||
Daniel Katz
|
||||
Daniel Shaulov
|
||||
Daniele Esposti
|
||||
Daniele Nicolodi
|
||||
Daniele Procida
|
||||
Daniil Konovalenko
|
||||
Danny Hermes
|
||||
Danny McClanahan
|
||||
Darren Kavanagh
|
||||
|
@ -200,6 +203,7 @@ Diego Caraballo
|
|||
Diego Ramirez
|
||||
DiegoCaraballo
|
||||
Dimitri Merejkowsky
|
||||
Dimitri Papadopoulos
|
||||
Dirk Stolle
|
||||
Dmitry Gladkov
|
||||
Dmitry Volodin
|
||||
|
@ -207,6 +211,7 @@ Domen Kožar
|
|||
Dominic Davis-Foster
|
||||
Donald Stufft
|
||||
Dongweiming
|
||||
doron zarhi
|
||||
Douglas Thor
|
||||
DrFeathers
|
||||
Dustin Ingram
|
||||
|
@ -282,6 +287,7 @@ hauntsaninja
|
|||
Henrich Hartzer
|
||||
Henry Schreiner
|
||||
Herbert Pfennig
|
||||
Holly Stotelmyer
|
||||
Hsiaoming Yang
|
||||
Hugo Lopes Tavares
|
||||
Hugo van Kemenade
|
||||
|
@ -305,6 +311,7 @@ Jacob Kim
|
|||
Jacob Walls
|
||||
Jaime Sanz
|
||||
jakirkham
|
||||
Jakub Kuczys
|
||||
Jakub Stasiak
|
||||
Jakub Vysoky
|
||||
Jakub Wilk
|
||||
|
@ -317,6 +324,7 @@ Jan Pokorný
|
|||
Jannis Leidel
|
||||
Jarek Potiuk
|
||||
jarondl
|
||||
Jason Curtis
|
||||
Jason R. Coombs
|
||||
Jay Graves
|
||||
Jean-Christophe Fillion-Robin
|
||||
|
@ -342,6 +350,7 @@ Jon Dufresne
|
|||
Jon Parise
|
||||
Jonas Nockert
|
||||
Jonathan Herbert
|
||||
Joonatan Partanen
|
||||
Joost Molenaar
|
||||
Jorge Niedbalski
|
||||
Joseph Bylund
|
||||
|
@ -350,6 +359,7 @@ Josh Bronson
|
|||
Josh Hansen
|
||||
Josh Schneier
|
||||
Juanjo Bazán
|
||||
Judah Rand
|
||||
Julian Berman
|
||||
Julian Gethmann
|
||||
Julien Demoor
|
||||
|
@ -455,6 +465,7 @@ Miro Hrončok
|
|||
Monica Baluna
|
||||
montefra
|
||||
Monty Taylor
|
||||
Muha Ajjan
|
||||
Nadav Wexler
|
||||
Nahuel Ambrosini
|
||||
Nate Coraor
|
||||
|
@ -484,6 +495,7 @@ nvdv
|
|||
OBITORASU
|
||||
Ofek Lev
|
||||
ofrinevo
|
||||
Oliver Freund
|
||||
Oliver Jeeves
|
||||
Oliver Mannion
|
||||
Oliver Tonnhofer
|
||||
|
@ -555,6 +567,7 @@ Riccardo Schirone
|
|||
Richard Jones
|
||||
Richard Si
|
||||
Ricky Ng-Adam
|
||||
Rishi
|
||||
RobberPhex
|
||||
Robert Collins
|
||||
Robert McGibbon
|
||||
|
@ -605,6 +618,7 @@ Stavros Korokithakis
|
|||
Stefan Scherfke
|
||||
Stefano Rivera
|
||||
Stephan Erb
|
||||
Stephen Rosen
|
||||
stepshal
|
||||
Steve (Gadget) Barnes
|
||||
Steve Barnes
|
||||
|
@ -644,6 +658,7 @@ Tom V
|
|||
Tomas Hrnciar
|
||||
Tomas Orsava
|
||||
Tomer Chachamu
|
||||
Tommi Enenkel | AnB
|
||||
Tomáš Hrnčiar
|
||||
Tony Beswick
|
||||
Tony Narlock
|
||||
|
@ -672,6 +687,7 @@ Wil Tan
|
|||
Wilfred Hughes
|
||||
William ML Leslie
|
||||
William T Olson
|
||||
William Woodruff
|
||||
Wilson Mo
|
||||
wim glenn
|
||||
Winson Luk
|
||||
|
|
|
@ -2,6 +2,7 @@ include AUTHORS.txt
|
|||
include LICENSE.txt
|
||||
include NEWS.rst
|
||||
include README.rst
|
||||
include SECURITY.md
|
||||
include pyproject.toml
|
||||
|
||||
include src/pip/_vendor/README.rst
|
||||
|
@ -18,6 +19,7 @@ exclude .mailmap
|
|||
exclude .appveyor.yml
|
||||
exclude .readthedocs.yml
|
||||
exclude .pre-commit-config.yaml
|
||||
exclude .readthedocs-custom-redirects.yml
|
||||
exclude tox.ini
|
||||
exclude noxfile.py
|
||||
|
||||
|
|
76
NEWS.rst
76
NEWS.rst
|
@ -9,6 +9,82 @@
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
23.0.1 (2023-02-17)
|
||||
===================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Ignore PIP_REQUIRE_VIRTUALENV for ``pip index`` (`#11671 <https://github.com/pypa/pip/issues/11671>`_)
|
||||
- Implement ``--break-system-packages`` to permit installing packages into
|
||||
``EXTERNALLY-MANAGED`` Python installations. (`#11780 <https://github.com/pypa/pip/issues/11780>`_)
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Improve handling of isolated build environments on platforms that
|
||||
customize the Python's installation schemes, such as Debian and
|
||||
Homebrew. (`#11740 <https://github.com/pypa/pip/issues/11740>`_)
|
||||
- Do not crash in presence of misformatted hash field in ``direct_url.json``. (`#11773 <https://github.com/pypa/pip/issues/11773>`_)
|
||||
|
||||
|
||||
23.0 (2023-01-30)
|
||||
=================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- 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>`_)
|
||||
- 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
|
||||
modify the externally managed environment. (`#11381 <https://github.com/pypa/pip/issues/11381>`_)
|
||||
- Enable the use of ``keyring`` found on ``PATH``. This allows ``keyring``
|
||||
installed using ``pipx`` to be used by ``pip``. (`#11589 <https://github.com/pypa/pip/issues/11589>`_)
|
||||
- The inspect and installation report formats are now declared stable, and their version
|
||||
has been bumped from ``0`` to ``1``. (`#11757 <https://github.com/pypa/pip/issues/11757>`_)
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Wheel cache behavior is restored to match previous versions, allowing the
|
||||
cache to find existing entries. (`#11527 <https://github.com/pypa/pip/issues/11527>`_)
|
||||
- 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
|
||||
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>`_)
|
||||
- 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>`_)
|
||||
- Stop checking that ``wheel`` is present when ``build-system.requires``
|
||||
is provided without ``build-system.build-backend`` as ``setuptools``
|
||||
(which we still check for) will inject it anyway. (`#11673 <https://github.com/pypa/pip/issues/11673>`_)
|
||||
- Fix an issue when an already existing in-memory distribution would cause
|
||||
exceptions in ``pip install`` (`#11704 <https://github.com/pypa/pip/issues/11704>`_)
|
||||
|
||||
Vendored Libraries
|
||||
------------------
|
||||
|
||||
- Upgrade certifi to 2022.12.7
|
||||
- Upgrade chardet to 5.1.0
|
||||
- Upgrade colorama to 0.4.6
|
||||
- Upgrade distro to 1.8.0
|
||||
- Remove pep517 from vendored packages
|
||||
- Upgrade platformdirs to 2.6.2
|
||||
- Add pyproject-hooks 1.0.0
|
||||
- Upgrade requests to 2.28.2
|
||||
- Upgrade rich to 12.6.0
|
||||
- Upgrade urllib3 to 1.26.14
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Fixed the description of the option "--install-options" in the documentation (`#10265 <https://github.com/pypa/pip/issues/10265>`_)
|
||||
- Remove mention that editable installs are necessary for pip freeze to report the VCS
|
||||
URL. (`#11675 <https://github.com/pypa/pip/issues/11675>`_)
|
||||
- Clarify that the egg URL fragment is only necessary for editable VCS installs, and
|
||||
otherwise not necessary anymore. (`#11676 <https://github.com/pypa/pip/issues/11676>`_)
|
||||
|
||||
|
||||
22.3.1 (2022-11-05)
|
||||
===================
|
||||
|
||||
|
|
3
SECURITY.md
Normal file
3
SECURITY.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Security and Vulnerability Reporting
|
||||
|
||||
If you find any security issues, please report to [security@python.org](mailto:security@python.org)
|
|
@ -11,7 +11,7 @@ Usage
|
|||
|
||||
.. tab:: Unix/macOS
|
||||
|
||||
.. pip-command-usage:: install "python -m pip"
|
||||
.. pip-command-usage:: install 'python -m pip'
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
|
@ -277,7 +277,7 @@ Examples
|
|||
.. code-block:: shell
|
||||
|
||||
python -m pip install SomePackage # latest version
|
||||
python -m pip install SomePackage==1.0.4 # specific version
|
||||
python -m pip install 'SomePackage==1.0.4' # specific version
|
||||
python -m pip install 'SomePackage>=1.0.4' # minimum version
|
||||
|
||||
.. tab:: Windows
|
||||
|
@ -285,8 +285,8 @@ Examples
|
|||
.. code-block:: shell
|
||||
|
||||
py -m pip install SomePackage # latest version
|
||||
py -m pip install SomePackage==1.0.4 # specific version
|
||||
py -m pip install 'SomePackage>=1.0.4' # minimum version
|
||||
py -m pip install "SomePackage==1.0.4" # specific version
|
||||
py -m pip install "SomePackage>=1.0.4" # minimum version
|
||||
|
||||
|
||||
#. Install a list of requirements specified in a file. See the :ref:`Requirements files <Requirements Files>`.
|
||||
|
@ -349,13 +349,13 @@ Examples
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install SomeProject@git+https://git.repo/some_pkg.git@1.3.1
|
||||
python -m pip install 'SomeProject@git+https://git.repo/some_pkg.git@1.3.1'
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
py -m pip install SomeProject@git+https://git.repo/some_pkg.git@1.3.1
|
||||
py -m pip install "SomeProject@git+https://git.repo/some_pkg.git@1.3.1"
|
||||
|
||||
|
||||
#. Install a project from VCS in "editable" mode. See the sections on :doc:`../topics/vcs-support` and :ref:`Editable Installs <editable-installs>`.
|
||||
|
@ -364,20 +364,20 @@ Examples
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install -e git+https://git.repo/some_pkg.git#egg=SomePackage # from git
|
||||
python -m pip install -e hg+https://hg.repo/some_pkg.git#egg=SomePackage # from mercurial
|
||||
python -m pip install -e svn+svn://svn.repo/some_pkg/trunk/#egg=SomePackage # from svn
|
||||
python -m pip install -e git+https://git.repo/some_pkg.git@feature#egg=SomePackage # from 'feature' branch
|
||||
python -m pip install -e "git+https://git.repo/some_repo.git#egg=subdir&subdirectory=subdir_path" # install a python package from a repo subdirectory
|
||||
python -m pip install -e 'git+https://git.repo/some_pkg.git#egg=SomePackage' # from git
|
||||
python -m pip install -e 'hg+https://hg.repo/some_pkg.git#egg=SomePackage' # from mercurial
|
||||
python -m pip install -e 'svn+svn://svn.repo/some_pkg/trunk/#egg=SomePackage' # from svn
|
||||
python -m pip install -e 'git+https://git.repo/some_pkg.git@feature#egg=SomePackage' # from 'feature' branch
|
||||
python -m pip install -e 'git+https://git.repo/some_repo.git#egg=subdir&subdirectory=subdir_path' # install a python package from a repo subdirectory
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
py -m pip install -e git+https://git.repo/some_pkg.git#egg=SomePackage # from git
|
||||
py -m pip install -e hg+https://hg.repo/some_pkg.git#egg=SomePackage # from mercurial
|
||||
py -m pip install -e svn+svn://svn.repo/some_pkg/trunk/#egg=SomePackage # from svn
|
||||
py -m pip install -e git+https://git.repo/some_pkg.git@feature#egg=SomePackage # from 'feature' branch
|
||||
py -m pip install -e "git+https://git.repo/some_pkg.git#egg=SomePackage" # from git
|
||||
py -m pip install -e "hg+https://hg.repo/some_pkg.git#egg=SomePackage" # from mercurial
|
||||
py -m pip install -e "svn+svn://svn.repo/some_pkg/trunk/#egg=SomePackage" # from svn
|
||||
py -m pip install -e "git+https://git.repo/some_pkg.git@feature#egg=SomePackage" # from 'feature' branch
|
||||
py -m pip install -e "git+https://git.repo/some_repo.git#egg=subdir&subdirectory=subdir_path" # install a python package from a repo subdirectory
|
||||
|
||||
#. Install a package with `extras`_.
|
||||
|
@ -386,21 +386,21 @@ Examples
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install SomePackage[PDF]
|
||||
python -m pip install "SomePackage[PDF] @ git+https://git.repo/SomePackage@main#subdirectory=subdir_path"
|
||||
python -m pip install .[PDF] # project in current directory
|
||||
python -m pip install SomePackage[PDF]==3.0
|
||||
python -m pip install SomePackage[PDF,EPUB] # multiple extras
|
||||
python -m pip install 'SomePackage[PDF]'
|
||||
python -m pip install 'SomePackage[PDF] @ git+https://git.repo/SomePackage@main#subdirectory=subdir_path'
|
||||
python -m pip install '.[PDF]' # project in current directory
|
||||
python -m pip install 'SomePackage[PDF]==3.0'
|
||||
python -m pip install 'SomePackage[PDF,EPUB]' # multiple extras
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
py -m pip install SomePackage[PDF]
|
||||
py -m pip install "SomePackage[PDF]"
|
||||
py -m pip install "SomePackage[PDF] @ git+https://git.repo/SomePackage@main#subdirectory=subdir_path"
|
||||
py -m pip install .[PDF] # project in current directory
|
||||
py -m pip install SomePackage[PDF]==3.0
|
||||
py -m pip install SomePackage[PDF,EPUB] # multiple extras
|
||||
py -m pip install ".[PDF]" # project in current directory
|
||||
py -m pip install "SomePackage[PDF]==3.0"
|
||||
py -m pip install "SomePackage[PDF,EPUB]" # multiple extras
|
||||
|
||||
#. Install a particular source archive file.
|
||||
|
||||
|
@ -408,15 +408,15 @@ Examples
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install ./downloads/SomePackage-1.0.4.tar.gz
|
||||
python -m pip install http://my.package.repo/SomePackage-1.0.4.zip
|
||||
python -m pip install './downloads/SomePackage-1.0.4.tar.gz'
|
||||
python -m pip install 'http://my.package.repo/SomePackage-1.0.4.zip'
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
py -m pip install ./downloads/SomePackage-1.0.4.tar.gz
|
||||
py -m pip install http://my.package.repo/SomePackage-1.0.4.zip
|
||||
py -m pip install "./downloads/SomePackage-1.0.4.tar.gz"
|
||||
py -m pip install "http://my.package.repo/SomePackage-1.0.4.zip"
|
||||
|
||||
#. Install a particular source archive file following :pep:`440` direct references.
|
||||
|
||||
|
@ -424,17 +424,17 @@ Examples
|
|||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m pip install SomeProject@http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl
|
||||
python -m pip install "SomeProject @ http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl"
|
||||
python -m pip install SomeProject@http://my.package.repo/1.2.3.tar.gz
|
||||
python -m pip install 'SomeProject@http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl'
|
||||
python -m pip install 'SomeProject @ http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl'
|
||||
python -m pip install 'SomeProject@http://my.package.repo/1.2.3.tar.gz'
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
py -m pip install SomeProject@http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl
|
||||
py -m pip install "SomeProject@http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl"
|
||||
py -m pip install "SomeProject @ http://my.package.repo/SomeProject-1.2.3-py33-none-any.whl"
|
||||
py -m pip install SomeProject@http://my.package.repo/1.2.3.tar.gz
|
||||
py -m pip install "SomeProject@http://my.package.repo/1.2.3.tar.gz"
|
||||
|
||||
#. Install from alternative package repositories.
|
||||
|
||||
|
|
|
@ -24,9 +24,9 @@ Things pip does:
|
|||
backwards compatibility reasons. But thing with setuptools:
|
||||
has a ``setup.py`` file that it invokes to …… get info?
|
||||
|
||||
2. Decides where to install stuff. Once the package is built, resulting
|
||||
artifact is then installed into system in appropriate place. :pep:`517`
|
||||
defines interface between build backend & installer.
|
||||
2. Decides where to install stuff. Once the package is built, the resulting
|
||||
artifact is then installed to the system in its appropriate place. :pep:`517`
|
||||
defines the interface between the build backend & installer.
|
||||
|
||||
Broad overview of flow
|
||||
======================
|
||||
|
@ -111,24 +111,24 @@ The package index gives pip a list of files for that package (via the existing P
|
|||
|
||||
pip chooses from the list a single file to download.
|
||||
|
||||
It may go back and choose another file to download
|
||||
It may go back and choose another file to download.
|
||||
|
||||
When pip looks at the package index, the place where it looks has
|
||||
basically a link. The link’s text is the name of the file
|
||||
basically a link. The link’s text is the name of the file.
|
||||
|
||||
This is the `PyPI Simple API`_ (PyPI has several APIs, some are being
|
||||
deprecated). pip looks at Simple API, documented initially at :pep:`503` --
|
||||
packaging.python.org has PyPA specifications with more details for
|
||||
Simple Repository API
|
||||
Simple Repository API.
|
||||
|
||||
For this package name -- this is the list of files available
|
||||
For this package name -- this is the list of files available.
|
||||
|
||||
Looks there for:
|
||||
|
||||
* The list of filenames
|
||||
* Other info
|
||||
|
||||
Once it has those, selects one file, downloads it
|
||||
Once it has those, it selects one file and downloads it.
|
||||
|
||||
(Question: If I want to ``pip install flask``, I think the whole list of filenames
|
||||
cannot….should not be …. ? I want only the Flask …. Why am I getting the
|
||||
|
|
|
@ -90,7 +90,7 @@ distro community, cloud provider support channels, etc).
|
|||
|
||||
## Upgrading `pip`
|
||||
|
||||
Upgrading your `pip` by running:
|
||||
Upgrade your `pip` by running:
|
||||
|
||||
```{pip-cli}
|
||||
$ pip install --upgrade pip
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
```{versionadded} 22.2
|
||||
```
|
||||
|
||||
```{versionchanged} 23.0
|
||||
``version`` has been bumped to ``1`` and the format declared stable.
|
||||
```
|
||||
|
||||
The `pip inspect` command produces a detailed JSON report of the Python
|
||||
environment, including installed distributions.
|
||||
|
||||
|
@ -10,10 +14,7 @@ environment, including installed distributions.
|
|||
|
||||
The report is a JSON object with the following properties:
|
||||
|
||||
- `version`: the string `0`, denoting that the inspect command is an experimental
|
||||
feature. This value will change to `1`, when the feature is deemed stable after
|
||||
gathering user feedback (likely in pip 22.3 or 23.0). Backward incompatible changes
|
||||
may be introduced in version `1` without notice. After that, it will change only if
|
||||
- `version`: the string `1`. It will change only if
|
||||
and when backward incompatible changes are introduced, such as removing mandatory
|
||||
fields or changing the semantics or data type of existing fields. The introduction of
|
||||
backward incompatible changes will follow the usual pip processes such as the
|
||||
|
@ -72,7 +73,7 @@ this (metadata abriged for brevity):
|
|||
|
||||
```json
|
||||
{
|
||||
"version": "0",
|
||||
"version": "1",
|
||||
"pip_version": "22.2.dev0",
|
||||
"installed": [
|
||||
{
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
```{versionadded} 22.2
|
||||
```
|
||||
|
||||
```{versionchanged} 23.0
|
||||
``version`` has been bumped to ``1`` and the format declared stable.
|
||||
```
|
||||
|
||||
The `--report` option of the pip install command produces a detailed JSON report of what
|
||||
it did install (or what it would have installed, if used with the `--dry-run` option).
|
||||
|
||||
|
@ -23,10 +27,7 @@ When considering use cases, please bear in mind that
|
|||
|
||||
The report is a JSON object with the following properties:
|
||||
|
||||
- `version`: the string `0`, denoting that the installation report is an experimental
|
||||
feature. This value will change to `1`, when the feature is deemed stable after
|
||||
gathering user feedback (likely in pip 22.3 or 23.0). Backward incompatible changes
|
||||
may be introduced in version `1` without notice. After that, it will change only if
|
||||
- `version`: the string `1`. It will change only if
|
||||
and when backward incompatible changes are introduced, such as removing mandatory
|
||||
fields or changing the semantics or data type of existing fields. The introduction of
|
||||
backward incompatible changes will follow the usual pip processes such as the
|
||||
|
@ -64,7 +65,7 @@ package with the following properties:
|
|||
`--find-links`.
|
||||
|
||||
```{note}
|
||||
For source archives, `download_info.archive_info.hash` may
|
||||
For source archives, `download_info.archive_info.hashes` may
|
||||
be absent when the requirement was installed from the wheel cache
|
||||
and the cache entry was populated by an older pip version that did not
|
||||
record the origin URL of the downloaded artifact.
|
||||
|
@ -92,14 +93,16 @@ will produce an output similar to this (metadata abriged for brevity):
|
|||
|
||||
```json
|
||||
{
|
||||
"version": "0",
|
||||
"version": "1",
|
||||
"pip_version": "22.2",
|
||||
"install": [
|
||||
{
|
||||
"download_info": {
|
||||
"url": "https://files.pythonhosted.org/packages/a4/0c/fbaa7319dcb5eecd3484686eb5a5c5702a6445adb566f01aee6de3369bc4/pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||
"archive_info": {
|
||||
"hash": "sha256=18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"
|
||||
"hashes": {
|
||||
"sha256": "18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"
|
||||
}
|
||||
}
|
||||
},
|
||||
"is_direct": false,
|
||||
|
@ -144,7 +147,9 @@ will produce an output similar to this (metadata abriged for brevity):
|
|||
"download_info": {
|
||||
"url": "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl",
|
||||
"archive_info": {
|
||||
"hash": "sha256=5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
|
||||
"hashes": {
|
||||
"sha256": "5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
|
||||
}
|
||||
}
|
||||
},
|
||||
"is_direct": false,
|
||||
|
@ -163,7 +168,9 @@ will produce an output similar to this (metadata abriged for brevity):
|
|||
"download_info": {
|
||||
"url": "https://files.pythonhosted.org/packages/75/e1/932e06004039dd670c9d5e1df0cd606bf46e29a28e65d5bb28e894ea29c9/typing_extensions-4.2.0-py3-none-any.whl",
|
||||
"archive_info": {
|
||||
"hash": "sha256=6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"
|
||||
"hashes": {
|
||||
"sha256": "6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"
|
||||
}
|
||||
}
|
||||
},
|
||||
"is_direct": false,
|
||||
|
|
|
@ -109,7 +109,6 @@ and two {ref}`--find-links <install_--find-links>` locations:
|
|||
|
||||
The options which can be applied to individual requirements are:
|
||||
|
||||
- {ref}`--install-option <install_--install-option>`
|
||||
- {ref}`--global-option <install_--global-option>`
|
||||
- {ref}`--config-settings <install_--config-settings>`
|
||||
- `--hash` (for {ref}`Hash-checking mode`)
|
||||
|
@ -161,7 +160,7 @@ This disables the use of wheels (cached or otherwise). This could mean that buil
|
|||
This mechanism is only preserved for backwards compatibility and should be considered deprecated. A future release of pip may drop these options.
|
||||
```
|
||||
|
||||
The `--global-option` and `--install-option` options are used to pass options to `setup.py`.
|
||||
The `--global-option` option is used to pass options to `setup.py`.
|
||||
|
||||
```{attention}
|
||||
These options are highly coupled with how pip invokes setuptools using the {doc}`../reference/build-system/setup-py` build system interface. It is not compatible with newer {doc}`../reference/build-system/pyproject-toml` build system interface.
|
||||
|
@ -171,15 +170,10 @@ This is will not work with other build-backends or newer setup.cfg-only projects
|
|||
|
||||
If you have a declaration like:
|
||||
|
||||
FooProject >= 1.2 --global-option="--no-user-cfg" \
|
||||
--install-option="--prefix='/usr/local'" \
|
||||
--install-option="--no-compile"
|
||||
FooProject >= 1.2 --global-option="--no-user-cfg"
|
||||
|
||||
The above translates roughly into running FooProject's `setup.py` script as:
|
||||
|
||||
python setup.py --no-user-cfg install --prefix='/usr/local' --no-compile
|
||||
python setup.py --no-user-cfg install
|
||||
|
||||
Note that the only way of giving more than one option to `setup.py` is through multiple `--global-option` and `--install-option` options, as shown in the example above. The value of each option is passed as a single argument to the `setup.py` script. Therefore, a line such as the following is invalid and would result in an installation error.
|
||||
|
||||
# Invalid. Please use '--install-option' twice as shown above.
|
||||
FooProject >= 1.2 --install-option="--prefix=/usr/local --no-compile"
|
||||
Note that the only way of giving more than one option to `setup.py` is through multiple `--global-option` options.
|
||||
|
|
|
@ -66,12 +66,120 @@ man pages][netrc-docs].
|
|||
## Keyring Support
|
||||
|
||||
pip supports loading credentials stored in your keyring using the
|
||||
{pypi}`keyring` library.
|
||||
{pypi}`keyring` library, which can be enabled py passing `--keyring-provider`
|
||||
with a value of `auto`, `disabled`, `import`, or `subprocess`. The default
|
||||
value `auto` respects `--no-input` and not query keyring at all if the option
|
||||
is used; otherwise it tries the `import`, `subprocess`, and `disabled`
|
||||
providers (in this order) and uses the first one that works.
|
||||
|
||||
### Configuring pip's keyring usage
|
||||
|
||||
Since the keyring configuration is likely system-wide, a more common way to
|
||||
configure its usage would be to use a configuration instead:
|
||||
|
||||
```{seealso}
|
||||
{doc}`./configuration` describes how pip configuration works.
|
||||
```
|
||||
|
||||
```bash
|
||||
$ pip install keyring # install keyring from PyPI
|
||||
$ pip config set --global global.keyring-provider subprocess
|
||||
|
||||
# A different user on the same system which has PYTHONPATH configured and and
|
||||
# wanting to use keyring installed that way could then run
|
||||
$ pip config set --user global.keyring-provider import
|
||||
|
||||
# For a specific virtual environment you might want to use disable it again
|
||||
# because you will only be using PyPI and the private repo (and mirror)
|
||||
# requires 2FA with a keycard and a pincode
|
||||
$ pip config set --site global.index https://pypi.org/simple
|
||||
$ pip config set --site global.keyring-provider disabled
|
||||
|
||||
# configuring it via environment variable is also possible
|
||||
$ export PIP_KEYRING_PROVIDER=disabled
|
||||
```
|
||||
|
||||
### Using keyring's Python module
|
||||
|
||||
Setting `keyring-provider` to `import` makes pip communicate with `keyring` via
|
||||
its Python interface.
|
||||
|
||||
```bash
|
||||
# install keyring from PyPI
|
||||
$ pip install keyring --index-url https://pypi.org/simple
|
||||
$ echo "your-password" | keyring set pypi.company.com your-username
|
||||
$ pip install your-package --index-url https://pypi.company.com/
|
||||
$ pip install your-package --keyring-provider import --index-url https://pypi.company.com/
|
||||
```
|
||||
|
||||
### Using keyring as a command line application
|
||||
|
||||
Setting `keyring-provider` to `subprocess` makes pip look for and use the
|
||||
`keyring` command found on `PATH`.
|
||||
|
||||
For this use case, a username *must* be included in the URL, since it is
|
||||
required by `keyring`'s command line interface. See the example below or the
|
||||
basic HTTP authentication section at the top of this page.
|
||||
|
||||
```bash
|
||||
# Install keyring from PyPI using pipx, which we assume is installed properly
|
||||
# you can also create a venv somewhere and add it to the PATH yourself instead
|
||||
$ pipx install keyring --index-url https://pypi.org/simple
|
||||
|
||||
# For Azure DevOps, also install its keyring backend.
|
||||
$ pipx inject keyring artifacts-keyring --index-url https://pypi.org/simple
|
||||
|
||||
# For Google Artifact Registry, also install and initialize its keyring backend.
|
||||
$ pipx inject keyring keyrings.google-artifactregistry-auth --index-url https://pypi.org/simple
|
||||
$ gcloud auth login
|
||||
|
||||
# Note that a username is required in the index URL.
|
||||
$ pip install your-package --keyring-provider subprocess --index-url https://username@pypi.example.com/
|
||||
```
|
||||
|
||||
### Here be dragons
|
||||
|
||||
The `auto` provider is conservative and does not query keyring at all when
|
||||
`--no-input` is used because the keyring might require user interaction such as
|
||||
prompting the user on the console. Third party tools frequently call Pip for
|
||||
you and do indeed pass `--no-input` as they are well-behaved and don't have
|
||||
much information to work with. (Keyring does have an api to request a backend
|
||||
that does not require user input.) You have more information about your system,
|
||||
however!
|
||||
|
||||
You can force keyring usage by requesting a keyring provider other than `auto`
|
||||
(or `disabled`). Leaving `import` and `subprocess`. You do this by passing
|
||||
`--keyring-provider import` or one of the following methods:
|
||||
|
||||
```bash
|
||||
# via config file, possibly with --user, --global or --site
|
||||
$ pip config set global.keyring-provider subprocess
|
||||
# or via environment variable
|
||||
$ export PIP_KEYRING_PROVIDER=import
|
||||
```
|
||||
|
||||
```{warning}
|
||||
Be careful when doing this since it could cause tools such as pipx and Pipenv
|
||||
to appear to hang. They show their own progress indicator while hiding output
|
||||
from the subprocess in which they run Pip. You won't know whether the keyring
|
||||
backend is waiting the user input or not in such situations.
|
||||
```
|
||||
|
||||
pip is conservative and does not query keyring at all when `--no-input` is used
|
||||
because the keyring might require user interaction such as prompting the user
|
||||
on the console. You can force keyring usage by passing `--force-keyring` or one
|
||||
of the following:
|
||||
|
||||
```bash
|
||||
# possibly with --user, --global or --site
|
||||
$ pip config set global.force-keyring true
|
||||
# or
|
||||
$ export PIP_FORCE_KEYRING=1
|
||||
```
|
||||
|
||||
```{warning}
|
||||
Be careful when doing this since it could cause tools such as pipx and Pipenv
|
||||
to appear to hang. They show their own progress indicator while hiding output
|
||||
from the subprocess in which they run Pip. You won't know whether the keyring
|
||||
backend is waiting the user input or not in such situations.
|
||||
```
|
||||
|
||||
Note that `keyring` (the Python package) needs to be installed separately from
|
||||
|
@ -79,5 +187,4 @@ pip. This can create a bootstrapping issue if you need the credentials stored in
|
|||
the keyring to download and install keyring.
|
||||
|
||||
It is, thus, expected that users that wish to use pip's keyring support have
|
||||
some mechanism for downloading and installing {pypi}`keyring` in their Python
|
||||
environment.
|
||||
some mechanism for downloading and installing {pypi}`keyring`.
|
||||
|
|
|
@ -67,7 +67,7 @@ You can use `pip cache dir` to get the cache directory that pip is currently con
|
|||
|
||||
### Default paths
|
||||
|
||||
````{tab} Unix
|
||||
````{tab} Linux
|
||||
```
|
||||
~/.cache/pip
|
||||
```
|
||||
|
|
|
@ -19,13 +19,14 @@ and how they are related to pip's various command line options.
|
|||
|
||||
## Configuration Files
|
||||
|
||||
Configuration files can change the default values for command line option.
|
||||
They are written using a standard INI style configuration files.
|
||||
Configuration files can change the default values for command line options.
|
||||
They are written using standard INI style configuration files.
|
||||
|
||||
pip has 3 "levels" of configuration files:
|
||||
pip has 4 "levels" of configuration files:
|
||||
|
||||
- `global`: system-wide configuration file, shared across users.
|
||||
- `user`: per-user configuration file.
|
||||
- `global`: system-wide configuration file, shared across all users.
|
||||
- `user`: per-user configuration file, shared across all environments.
|
||||
- `base` : per-base environment configuration file, shared across all virtualenvs with the same base. (available since pip 23.0)
|
||||
- `site`: per-environment configuration file; i.e. per-virtualenv.
|
||||
|
||||
### Location
|
||||
|
@ -47,6 +48,9 @@ User
|
|||
|
||||
The legacy "per-user" configuration file is also loaded, if it exists: {file}`$HOME/.pip/pip.conf`.
|
||||
|
||||
Base
|
||||
: {file}`\{sys.base_prefix\}/pip.conf`
|
||||
|
||||
Site
|
||||
: {file}`$VIRTUAL_ENV/pip.conf`
|
||||
```
|
||||
|
@ -63,6 +67,9 @@ User
|
|||
|
||||
The legacy "per-user" configuration file is also loaded, if it exists: {file}`$HOME/.pip/pip.conf`.
|
||||
|
||||
Base
|
||||
: {file}`\{sys.base_prefix\}/pip.conf`
|
||||
|
||||
Site
|
||||
: {file}`$VIRTUAL_ENV/pip.conf`
|
||||
```
|
||||
|
@ -81,6 +88,9 @@ User
|
|||
|
||||
The legacy "per-user" configuration file is also loaded, if it exists: {file}`%HOME%\\pip\\pip.ini`
|
||||
|
||||
Base
|
||||
: {file}`\{sys.base_prefix\}\\pip.ini`
|
||||
|
||||
Site
|
||||
: {file}`%VIRTUAL_ENV%\\pip.ini`
|
||||
```
|
||||
|
@ -102,6 +112,7 @@ order:
|
|||
- `PIP_CONFIG_FILE`, if given.
|
||||
- Global
|
||||
- User
|
||||
- Base
|
||||
- Site
|
||||
|
||||
Each file read overrides any values read from previous files, so if the
|
||||
|
|
|
@ -34,7 +34,7 @@ Editable installs allow you to install your project without copying any files. I
|
|||
With an editable install, you only need to perform a re-installation if you change the project metadata (eg: version, what scripts need to be generated etc). You will still need to run build commands when you need to perform a compilation for non-Python code in the project (eg: C extensions).
|
||||
|
||||
```{caution}
|
||||
It is possible to see behaviour differences between regular installs vs editable installs. In case you distribute the project as a "distribution package", users will see the behaviour of regular installs -- thus, it is important to ensure that regular installs work correctly.
|
||||
It is possible to see behaviour differences between regular installs vs editable installs. These differences depend on the build-backend, and you should check the build-backend documentation for the details. In case you distribute the project as a "distribution package", users will see the behaviour of regular installs -- thus, it is important to ensure that regular installs work correctly.
|
||||
```
|
||||
|
||||
```{note}
|
||||
|
|
|
@ -18,7 +18,7 @@ The supported schemes are `git+file`, `git+https`, `git+ssh`, `git+http`,
|
|||
`git+git` and `git`. Here are some of the supported forms:
|
||||
|
||||
```none
|
||||
MyProject @ git+ssh://git.example.com/MyProject
|
||||
MyProject @ git+ssh://git@git.example.com/MyProject
|
||||
MyProject @ git+file:///home/user/projects/MyProject
|
||||
MyProject @ git+https://git.example.com/MyProject
|
||||
```
|
||||
|
@ -130,18 +130,19 @@ VCS source will not overwrite it without an `--upgrade` flag. Further, pip
|
|||
looks at the package version, at the target revision to determine what action to
|
||||
take on the VCS requirement (not the commit itself).
|
||||
|
||||
The {ref}`pip freeze` subcommand will record the VCS requirement specifier
|
||||
(referencing a specific commit) only if the install is done with the editable
|
||||
option.
|
||||
|
||||
## URL fragments
|
||||
|
||||
pip looks at 2 fragments for VCS URLs:
|
||||
pip looks at the `subdirectory` fragments of VCS URLs for specifying the path to the
|
||||
Python package, when it is not in the root of the VCS directory. eg: `pkg_dir`.
|
||||
|
||||
- `egg`: For specifying the "project name" for use in pip's dependency
|
||||
resolution logic. eg: `egg=project_name`
|
||||
- `subdirectory`: For specifying the path to the Python package, when it is not
|
||||
in the root of the VCS directory. eg: `pkg_dir`
|
||||
pip also looks at the `egg` fragment specifying the "project name". In practice the
|
||||
`egg` fragment is only required to help pip determine the VCS clone location in editable
|
||||
mode. In all other circumstances, the `egg` fragment is not necessary and its use is
|
||||
discouraged.
|
||||
|
||||
The `egg` fragment **should** be a bare
|
||||
[PEP 508](https://peps.python.org/pep-0508/) project name. Anything else
|
||||
is not guaranteed to work.
|
||||
|
||||
````{admonition} Example
|
||||
If your repository layout is:
|
||||
|
@ -157,6 +158,12 @@ some_other_file
|
|||
|
||||
Then, to install from this repository, the syntax would be:
|
||||
|
||||
```{pip-cli}
|
||||
$ pip install "pkg @ vcs+protocol://repo_url/#subdirectory=pkg_dir"
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```{pip-cli}
|
||||
$ pip install -e "vcs+protocol://repo_url/#egg=pkg&subdirectory=pkg_dir"
|
||||
```
|
||||
|
|
|
@ -800,7 +800,7 @@ As noted previously, pip is a command line program. While it is implemented in
|
|||
Python, and so is available from your Python code via ``import pip``, you must
|
||||
not use pip's internal APIs in this way. There are a number of reasons for this:
|
||||
|
||||
#. The pip code assumes that is in sole control of the global state of the
|
||||
#. The pip code assumes that it is in sole control of the global state of the
|
||||
program.
|
||||
pip manages things like the logging system configuration, or the values of
|
||||
the standard IO streams, without considering the possibility that user code
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Fixed the description of the option "--install-options" in the documentation
|
1
news/11169.feature.rst
Normal file
1
news/11169.feature.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Display dependency chain on each Collecting/Processing log line.
|
1
news/11358.removal.rst
Normal file
1
news/11358.removal.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Remove support for the deprecated ``--install-options``.
|
2
news/11451.removal.rst
Normal file
2
news/11451.removal.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
``--no-binary`` does not imply ``setup.py install`` anymore. Instead a wheel will be
|
||||
built locally and installed.
|
1
news/11529.bugfix.rst
Normal file
1
news/11529.bugfix.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Fix grammar by changing "A new release of pip available:" to "A new release of pip is available:" in the notice used for indicating that.
|
|
@ -1,3 +0,0 @@
|
|||
Fix entry point generation of ``pip.X``, ``pipX.Y``, and ``easy_install-X.Y``
|
||||
to correctly account for multi-digit Python version segments (e.g. the "11"
|
||||
part of 3.11).
|
|
@ -1,2 +0,0 @@
|
|||
Enable the use of ``keyring`` found on ``PATH``. This allows ``keyring``
|
||||
installed using ``pipx`` to be used by ``pip``.
|
|
@ -1 +0,0 @@
|
|||
Use the "venv" scheme if available to obtain prefixed lib paths.
|
4
news/11681.feature.rst
Normal file
4
news/11681.feature.rst
Normal file
|
@ -0,0 +1,4 @@
|
|||
The ``--config-settings``/``-C`` option now supports using the same key multiple
|
||||
times. When the same key is specified multiple times, all values are passed to
|
||||
the build backend as a list, as opposed to the previous behavior, where pip would
|
||||
only pass the last value if the same key was used multiple times.
|
1
news/11774.bugfix.rst
Normal file
1
news/11774.bugfix.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Correct the way to decide if keyring is available.
|
2
news/11775.doc.rst
Normal file
2
news/11775.doc.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Cross-reference the ``--python`` flag from the ``--prefix`` flag,
|
||||
and mention limitations of ``--prefix`` regarding script installation.
|
1
news/11809.doc.rst
Normal file
1
news/11809.doc.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Add SECURITY.md to make the policy offical.
|
1
news/11837.bugfix.rst
Normal file
1
news/11837.bugfix.rst
Normal file
|
@ -0,0 +1 @@
|
|||
More consistent resolution backtracking by removing legacy hack related to setuptools resolution
|
1
news/11838.doc.rst
Normal file
1
news/11838.doc.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Add username to Git over SSH example.
|
2
news/11842.doc.rst
Normal file
2
news/11842.doc.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Quote extras in the pip install docs to guard shells with default glob
|
||||
qualifiers, like zsh.
|
1
news/8719.feature.rst
Normal file
1
news/8719.feature.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Add ``--keyring-provider`` flag. See the Authentication page in the documentation for more info.
|
1
news/9752.feature.rst
Normal file
1
news/9752.feature.rst
Normal file
|
@ -0,0 +1 @@
|
|||
In the case of virtual environments, configuration files are now also included from the base installation.
|
|
@ -1 +0,0 @@
|
|||
Upgrade colorama to 0.4.6
|
|
@ -1 +0,0 @@
|
|||
Upgrade distro to 1.8.0
|
1
news/pkg_resources.vendor.rst
Normal file
1
news/pkg_resources.vendor.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Patch pkg_resources to remove dependency on ``jaraco.text``.
|
|
@ -1 +0,0 @@
|
|||
Upgrade platformdirs to 2.5.3
|
1
news/resolvelib.vendor.rst
Normal file
1
news/resolvelib.vendor.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Upgrade resolvelib to 0.9.0
|
1
news/setuptools.vendor.rst
Normal file
1
news/setuptools.vendor.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Update pkg_resources (via setuptools) to 65.6.3
|
1
news/short-config-settings-option.feature.rst
Normal file
1
news/short-config-settings-option.feature.rst
Normal file
|
@ -0,0 +1 @@
|
|||
Add ``-C`` as a short version of the ``--config-settings`` option.
|
13
noxfile.py
13
noxfile.py
|
@ -1,6 +1,7 @@
|
|||
"""Automation using nox.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
|
@ -183,7 +184,13 @@ def lint(session: nox.Session) -> None:
|
|||
def vendoring(session: nox.Session) -> None:
|
||||
session.install("vendoring~=1.2.0")
|
||||
|
||||
if "--upgrade" not in session.posargs:
|
||||
parser = argparse.ArgumentParser(prog="nox -s vendoring")
|
||||
parser.add_argument("--upgrade-all", action="store_true")
|
||||
parser.add_argument("--upgrade", action="append", default=[])
|
||||
parser.add_argument("--skip", action="append", default=[])
|
||||
args = parser.parse_args(session.posargs)
|
||||
|
||||
if not (args.upgrade or args.upgrade_all):
|
||||
session.run("vendoring", "sync", "-v")
|
||||
return
|
||||
|
||||
|
@ -199,7 +206,9 @@ def vendoring(session: nox.Session) -> None:
|
|||
|
||||
vendor_txt = Path("src/pip/_vendor/vendor.txt")
|
||||
for name, old_version in pinned_requirements(vendor_txt):
|
||||
if name == "setuptools":
|
||||
if name in args.skip:
|
||||
continue
|
||||
if args.upgrade and name not in args.upgrade:
|
||||
continue
|
||||
|
||||
# update requirements.txt
|
||||
|
|
|
@ -50,6 +50,8 @@ drop = [
|
|||
"easy_install.py",
|
||||
"setuptools",
|
||||
"pkg_resources/_vendor/",
|
||||
"_distutils_hack",
|
||||
"distutils-precedence.pth",
|
||||
"pkg_resources/extern/",
|
||||
# trim vendored pygments styles and lexers
|
||||
"pygments/styles/[!_]*.py",
|
||||
|
|
|
@ -42,6 +42,9 @@ disallow_any_generics = True
|
|||
warn_unused_ignores = True
|
||||
no_implicit_optional = True
|
||||
|
||||
[mypy-pip._internal.utils._jaraco_text]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-pip._vendor.*]
|
||||
ignore_errors = True
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import List, Optional
|
||||
|
||||
__version__ = "23.0.dev0"
|
||||
__version__ = "23.1.dev0"
|
||||
|
||||
|
||||
def main(args: Optional[List[str]] = None) -> int:
|
||||
|
|
|
@ -8,9 +8,8 @@ import site
|
|||
import sys
|
||||
import textwrap
|
||||
from collections import OrderedDict
|
||||
from sysconfig import get_paths
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type
|
||||
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
|
||||
|
||||
from pip._vendor.certifi import where
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
@ -18,11 +17,7 @@ from pip._vendor.packaging.version import Version
|
|||
|
||||
from pip import __file__ as pip_location
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.locations import (
|
||||
get_isolated_environment_lib_paths,
|
||||
get_platlib,
|
||||
get_purelib,
|
||||
)
|
||||
from pip._internal.locations import get_platlib, get_purelib, get_scheme
|
||||
from pip._internal.metadata import get_default_environment, get_environment
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
|
@ -33,15 +28,17 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
|
||||
return (a, b) if a != b else (a,)
|
||||
|
||||
|
||||
class _Prefix:
|
||||
def __init__(self, path: str) -> None:
|
||||
self.path = path
|
||||
self.setup = False
|
||||
self.bin_dir = get_paths(
|
||||
"nt" if os.name == "nt" else "posix_prefix",
|
||||
vars={"base": path, "platbase": path},
|
||||
)["scripts"]
|
||||
self.lib_dirs = get_isolated_environment_lib_paths(path)
|
||||
scheme = get_scheme("", prefix=path)
|
||||
self.bin_dir = scheme.scripts
|
||||
self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
|
||||
|
||||
|
||||
def get_runnable_pip() -> str:
|
||||
|
|
|
@ -164,6 +164,14 @@ require_virtualenv: Callable[..., Option] = partial(
|
|||
),
|
||||
)
|
||||
|
||||
override_externally_managed: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--break-system-packages",
|
||||
dest="override_externally_managed",
|
||||
action="store_true",
|
||||
help="Allow pip to modify an EXTERNALLY-MANAGED Python installation",
|
||||
)
|
||||
|
||||
python: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--python",
|
||||
|
@ -244,6 +252,19 @@ no_input: Callable[..., Option] = partial(
|
|||
help="Disable prompting for input.",
|
||||
)
|
||||
|
||||
keyring_provider: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--keyring-provider",
|
||||
dest="keyring_provider",
|
||||
choices=["auto", "disabled", "import", "subprocess"],
|
||||
default="disabled",
|
||||
help=(
|
||||
"Enable the credential lookup via the keyring library if user input is allowed."
|
||||
" Specify which mechanism to use [disabled, import, subprocess]."
|
||||
" (default: disabled)"
|
||||
),
|
||||
)
|
||||
|
||||
proxy: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--proxy",
|
||||
|
@ -803,11 +824,18 @@ def _handle_config_settings(
|
|||
if dest is None:
|
||||
dest = {}
|
||||
setattr(parser.values, option.dest, dest)
|
||||
dest[key] = val
|
||||
if key in dest:
|
||||
if isinstance(dest[key], list):
|
||||
dest[key].append(val)
|
||||
else:
|
||||
dest[key] = [dest[key], val]
|
||||
else:
|
||||
dest[key] = val
|
||||
|
||||
|
||||
config_settings: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"-C",
|
||||
"--config-settings",
|
||||
dest="config_settings",
|
||||
type=str,
|
||||
|
@ -819,17 +847,6 @@ config_settings: Callable[..., Option] = partial(
|
|||
"to pass multiple keys to the backend.",
|
||||
)
|
||||
|
||||
install_options: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--install-option",
|
||||
dest="install_options",
|
||||
action="append",
|
||||
metavar="options",
|
||||
help="This option is deprecated. Using this option with location-changing "
|
||||
"options may cause unexpected behavior. "
|
||||
"Use pip-level options like --user, --prefix, --root, and --target.",
|
||||
)
|
||||
|
||||
build_options: Callable[..., Option] = partial(
|
||||
Option,
|
||||
"--build-option",
|
||||
|
@ -1019,6 +1036,7 @@ general_group: Dict[str, Any] = {
|
|||
quiet,
|
||||
log,
|
||||
no_input,
|
||||
keyring_provider,
|
||||
proxy,
|
||||
retries,
|
||||
timeout,
|
||||
|
|
|
@ -151,6 +151,7 @@ class SessionCommandMixin(CommandContextMixIn):
|
|||
|
||||
# Determine if we can prompt the user for authentication or not
|
||||
session.auth.prompting = not options.no_input
|
||||
session.auth.keyring_provider = options.keyring_provider
|
||||
|
||||
return session
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ def create_vendor_txt_map() -> Dict[str, str]:
|
|||
|
||||
def get_module_from_module_name(module_name: str) -> ModuleType:
|
||||
# Module name can be uppercase in vendor.txt for some reason...
|
||||
module_name = module_name.lower()
|
||||
module_name = module_name.lower().replace("-", "_")
|
||||
# PATCH: setuptools is actually only pkg_resources.
|
||||
if module_name == "setuptools":
|
||||
module_name = "pkg_resources"
|
||||
|
|
|
@ -8,10 +8,7 @@ from pip._internal.cli.cmdoptions import make_target_python
|
|||
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pip._internal.req.req_install import (
|
||||
LegacySetupPyOptionsCheckMode,
|
||||
check_legacy_setup_py_options,
|
||||
)
|
||||
from pip._internal.req.req_install import check_legacy_setup_py_options
|
||||
from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
|
@ -109,9 +106,7 @@ class DownloadCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
check_legacy_setup_py_options(
|
||||
options, reqs, LegacySetupPyOptionsCheckMode.DOWNLOAD
|
||||
)
|
||||
check_legacy_setup_py_options(options, reqs)
|
||||
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
|
|
|
@ -24,6 +24,7 @@ class IndexCommand(IndexGroupCommand):
|
|||
Inspect information available from package indexes.
|
||||
"""
|
||||
|
||||
ignore_require_venv = True
|
||||
usage = """
|
||||
%prog versions <package>
|
||||
"""
|
||||
|
|
|
@ -46,11 +46,6 @@ class InspectCommand(Command):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
logger.warning(
|
||||
"pip inspect is currently an experimental command. "
|
||||
"The output format may change in a future release without prior warning."
|
||||
)
|
||||
|
||||
cmdoptions.check_list_path_option(options)
|
||||
dists = get_environment(options.path).iter_installed_distributions(
|
||||
local_only=options.local,
|
||||
|
@ -58,7 +53,7 @@ class InspectCommand(Command):
|
|||
skip=set(stdlib_pkgs),
|
||||
)
|
||||
output = {
|
||||
"version": "0",
|
||||
"version": "1",
|
||||
"pip_version": __version__,
|
||||
"installed": [self._dist_to_dict(dist) for dist in dists],
|
||||
"environment": default_environment(),
|
||||
|
|
|
@ -5,9 +5,8 @@ import os
|
|||
import shutil
|
||||
import site
|
||||
from optparse import SUPPRESS_HELP, Values
|
||||
from typing import Iterable, List, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.rich import print_json
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
|
@ -22,14 +21,12 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS
|
|||
from pip._internal.exceptions import CommandError, InstallationError
|
||||
from pip._internal.locations import get_scheme
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.models.format_control import FormatControl
|
||||
from pip._internal.models.installation_report import InstallationReport
|
||||
from pip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pip._internal.operations.check import ConflictDetails, check_install_conflicts
|
||||
from pip._internal.req import install_given_reqs
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
LegacySetupPyOptionsCheckMode,
|
||||
check_legacy_setup_py_options,
|
||||
)
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
|
@ -37,10 +34,10 @@ from pip._internal.utils.deprecation import (
|
|||
LegacyInstallReasonFailedBdistWheel,
|
||||
deprecated,
|
||||
)
|
||||
from pip._internal.utils.distutils_args import parse_distutils_args
|
||||
from pip._internal.utils.filesystem import test_writable_dir
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.misc import (
|
||||
check_externally_managed,
|
||||
ensure_dir,
|
||||
get_pip_version,
|
||||
protect_pip_from_modification_on_windows,
|
||||
|
@ -51,26 +48,11 @@ from pip._internal.utils.virtualenv import (
|
|||
running_under_virtualenv,
|
||||
virtualenv_no_global,
|
||||
)
|
||||
from pip._internal.wheel_builder import (
|
||||
BdistWheelAllowedPredicate,
|
||||
build,
|
||||
should_build_for_install_command,
|
||||
)
|
||||
from pip._internal.wheel_builder import build, should_build_for_install_command
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def get_check_bdist_wheel_allowed(
|
||||
format_control: FormatControl,
|
||||
) -> BdistWheelAllowedPredicate:
|
||||
def check_binary_allowed(req: InstallRequirement) -> bool:
|
||||
canonical_name = canonicalize_name(req.name or "")
|
||||
allowed_formats = format_control.get_allowed_formats(canonical_name)
|
||||
return "binary" in allowed_formats
|
||||
|
||||
return check_binary_allowed
|
||||
|
||||
|
||||
class InstallCommand(RequirementCommand):
|
||||
"""
|
||||
Install packages from:
|
||||
|
@ -155,7 +137,12 @@ class InstallCommand(RequirementCommand):
|
|||
default=None,
|
||||
help=(
|
||||
"Installation prefix where lib, bin and other top-level "
|
||||
"folders are placed"
|
||||
"folders are placed. Note that the resulting installation may "
|
||||
"contain scripts and other resources which reference the "
|
||||
"Python interpreter of pip, and not that of ``--prefix``. "
|
||||
"See also the ``--python`` option if the intention is to "
|
||||
"install packages into another (possibly pip-free) "
|
||||
"environment."
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -214,9 +201,9 @@ class InstallCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
|
||||
self.cmd_opts.add_option(cmdoptions.check_build_deps())
|
||||
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.config_settings())
|
||||
self.cmd_opts.add_option(cmdoptions.install_options())
|
||||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
|
@ -284,14 +271,29 @@ class InstallCommand(RequirementCommand):
|
|||
if options.use_user_site and options.target_dir is not None:
|
||||
raise CommandError("Can not combine '--user' and '--target'")
|
||||
|
||||
# Check whether the environment we're installing into is externally
|
||||
# managed, as specified in PEP 668. Specifying --root, --target, or
|
||||
# --prefix disables the check, since there's no reliable way to locate
|
||||
# the EXTERNALLY-MANAGED file for those cases. An exception is also
|
||||
# made specifically for "--dry-run --report" for convenience.
|
||||
installing_into_current_environment = (
|
||||
not (options.dry_run and options.json_report_file)
|
||||
and options.root_path is None
|
||||
and options.target_dir is None
|
||||
and options.prefix_path is None
|
||||
)
|
||||
if (
|
||||
installing_into_current_environment
|
||||
and not options.override_externally_managed
|
||||
):
|
||||
check_externally_managed()
|
||||
|
||||
upgrade_strategy = "to-satisfy-only"
|
||||
if options.upgrade:
|
||||
upgrade_strategy = options.upgrade_strategy
|
||||
|
||||
cmdoptions.check_dist_restriction(options, check_target=True)
|
||||
|
||||
install_options = options.install_options or []
|
||||
|
||||
logger.verbose("Using %s", get_pip_version())
|
||||
options.use_user_site = decide_user_install(
|
||||
options.use_user_site,
|
||||
|
@ -342,9 +344,7 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
try:
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
check_legacy_setup_py_options(
|
||||
options, reqs, LegacySetupPyOptionsCheckMode.INSTALL
|
||||
)
|
||||
check_legacy_setup_py_options(options, reqs)
|
||||
|
||||
if "no-binary-enable-wheel-cache" in options.features_enabled:
|
||||
# TODO: remove format_control from WheelCache when the deprecation cycle
|
||||
|
@ -371,8 +371,6 @@ class InstallCommand(RequirementCommand):
|
|||
for req in reqs:
|
||||
req.permit_editable_wheels = True
|
||||
|
||||
reject_location_related_install_options(reqs, options.install_options)
|
||||
|
||||
preparer = self.make_requirement_preparer(
|
||||
temp_build_dir=directory,
|
||||
options=options,
|
||||
|
@ -402,12 +400,6 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
if options.json_report_file:
|
||||
logger.warning(
|
||||
"--report is currently an experimental option. "
|
||||
"The output format may change in a future release "
|
||||
"without prior warning."
|
||||
)
|
||||
|
||||
report = InstallationReport(requirement_set.requirements_to_install)
|
||||
if options.json_report_file == "-":
|
||||
print_json(data=report.to_dict())
|
||||
|
@ -437,14 +429,10 @@ class InstallCommand(RequirementCommand):
|
|||
modifying_pip = pip_req.satisfied_by is None
|
||||
protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
|
||||
|
||||
check_bdist_wheel_allowed = get_check_bdist_wheel_allowed(
|
||||
finder.format_control
|
||||
)
|
||||
|
||||
reqs_to_build = [
|
||||
r
|
||||
for r in requirement_set.requirements.values()
|
||||
if should_build_for_install_command(r, check_bdist_wheel_allowed)
|
||||
if should_build_for_install_command(r)
|
||||
]
|
||||
|
||||
_, build_failures = build(
|
||||
|
@ -493,7 +481,6 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
installed = install_given_reqs(
|
||||
to_install,
|
||||
install_options,
|
||||
global_options,
|
||||
root=options.root_path,
|
||||
home=target_temp_dir_path,
|
||||
|
@ -764,45 +751,6 @@ def decide_user_install(
|
|||
return True
|
||||
|
||||
|
||||
def reject_location_related_install_options(
|
||||
requirements: List[InstallRequirement], options: Optional[List[str]]
|
||||
) -> None:
|
||||
"""If any location-changing --install-option arguments were passed for
|
||||
requirements or on the command-line, then show a deprecation warning.
|
||||
"""
|
||||
|
||||
def format_options(option_names: Iterable[str]) -> List[str]:
|
||||
return ["--{}".format(name.replace("_", "-")) for name in option_names]
|
||||
|
||||
offenders = []
|
||||
|
||||
for requirement in requirements:
|
||||
install_options = requirement.install_options
|
||||
location_options = parse_distutils_args(install_options)
|
||||
if location_options:
|
||||
offenders.append(
|
||||
"{!r} from {}".format(
|
||||
format_options(location_options.keys()), requirement
|
||||
)
|
||||
)
|
||||
|
||||
if options:
|
||||
location_options = parse_distutils_args(options)
|
||||
if location_options:
|
||||
offenders.append(
|
||||
"{!r} from command line".format(format_options(location_options.keys()))
|
||||
)
|
||||
|
||||
if not offenders:
|
||||
return
|
||||
|
||||
raise CommandError(
|
||||
"Location-changing options found in --install-option: {}."
|
||||
" This is unsupported, use pip-level options like --user,"
|
||||
" --prefix, --root, and --target instead.".format("; ".join(offenders))
|
||||
)
|
||||
|
||||
|
||||
def create_os_error_message(
|
||||
error: OSError, show_traceback: bool, using_user_site: bool
|
||||
) -> str:
|
||||
|
|
|
@ -53,6 +53,7 @@ class _PackageInfo(NamedTuple):
|
|||
name: str
|
||||
version: str
|
||||
location: str
|
||||
editable_project_location: Optional[str]
|
||||
requires: List[str]
|
||||
required_by: List[str]
|
||||
installer: str
|
||||
|
@ -120,6 +121,7 @@ def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None
|
|||
name=dist.raw_name,
|
||||
version=str(dist.version),
|
||||
location=dist.location or "",
|
||||
editable_project_location=dist.editable_project_location,
|
||||
requires=requires,
|
||||
required_by=required_by,
|
||||
installer=dist.installer,
|
||||
|
@ -158,6 +160,10 @@ def print_results(
|
|||
write_output("Author-email: %s", dist.author_email)
|
||||
write_output("License: %s", dist.license)
|
||||
write_output("Location: %s", dist.location)
|
||||
if dist.editable_project_location is not None:
|
||||
write_output(
|
||||
"Editable project location: %s", dist.editable_project_location
|
||||
)
|
||||
write_output("Requires: %s", ", ".join(dist.requires))
|
||||
write_output("Required-by: %s", ", ".join(dist.required_by))
|
||||
|
||||
|
|
|
@ -14,7 +14,10 @@ from pip._internal.req.constructors import (
|
|||
install_req_from_line,
|
||||
install_req_from_parsed_requirement,
|
||||
)
|
||||
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
|
||||
from pip._internal.utils.misc import (
|
||||
check_externally_managed,
|
||||
protect_pip_from_modification_on_windows,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -55,6 +58,7 @@ class UninstallCommand(Command, SessionCommandMixin):
|
|||
help="Don't ask for confirmation of uninstall deletions.",
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.root_user_action())
|
||||
self.cmd_opts.add_option(cmdoptions.override_externally_managed())
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
|
@ -90,6 +94,9 @@ class UninstallCommand(Command, SessionCommandMixin):
|
|||
f'"pip help {self.name}")'
|
||||
)
|
||||
|
||||
if not options.override_externally_managed:
|
||||
check_externally_managed()
|
||||
|
||||
protect_pip_from_modification_on_windows(
|
||||
modifying_pip="pip" in reqs_to_uninstall
|
||||
)
|
||||
|
|
|
@ -12,7 +12,6 @@ from pip._internal.exceptions import CommandError
|
|||
from pip._internal.operations.build.build_tracker import get_build_tracker
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
LegacySetupPyOptionsCheckMode,
|
||||
check_legacy_setup_py_options,
|
||||
)
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
|
@ -122,9 +121,7 @@ class WheelCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
reqs = self.get_requirements(args, options, finder, session)
|
||||
check_legacy_setup_py_options(
|
||||
options, reqs, LegacySetupPyOptionsCheckMode.WHEEL
|
||||
)
|
||||
check_legacy_setup_py_options(options, reqs)
|
||||
|
||||
if "no-binary-enable-wheel-cache" in options.features_enabled:
|
||||
# TODO: remove format_control from WheelCache when the deprecation cycle
|
||||
|
|
|
@ -36,12 +36,20 @@ ENV_NAMES_IGNORED = "version", "help"
|
|||
kinds = enum(
|
||||
USER="user", # User Specific
|
||||
GLOBAL="global", # System Wide
|
||||
SITE="site", # [Virtual] Environment Specific
|
||||
BASE="base", # Base environment specific (e.g. for all venvs with the same base)
|
||||
SITE="site", # Environment Specific (e.g. per venv)
|
||||
ENV="env", # from PIP_CONFIG_FILE
|
||||
ENV_VAR="env-var", # from Environment Variables
|
||||
)
|
||||
OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
|
||||
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
|
||||
OVERRIDE_ORDER = (
|
||||
kinds.GLOBAL,
|
||||
kinds.USER,
|
||||
kinds.BASE,
|
||||
kinds.SITE,
|
||||
kinds.ENV,
|
||||
kinds.ENV_VAR,
|
||||
)
|
||||
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.BASE, kinds.SITE
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -70,6 +78,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
|
|||
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
|
||||
]
|
||||
|
||||
base_config_file = os.path.join(sys.base_prefix, CONFIG_BASENAME)
|
||||
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
|
||||
legacy_config_file = os.path.join(
|
||||
os.path.expanduser("~"),
|
||||
|
@ -78,6 +87,7 @@ def get_configuration_files() -> Dict[Kind, List[str]]:
|
|||
)
|
||||
new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
|
||||
return {
|
||||
kinds.BASE: [base_config_file],
|
||||
kinds.GLOBAL: global_config_files,
|
||||
kinds.SITE: [site_config_file],
|
||||
kinds.USER: [legacy_config_file, new_config_file],
|
||||
|
@ -344,6 +354,8 @@ class Configuration:
|
|||
# The legacy config file is overridden by the new config file
|
||||
yield kinds.USER, config_files[kinds.USER]
|
||||
|
||||
yield kinds.BASE, config_files[kinds.BASE]
|
||||
|
||||
# finally virtualenv configuration first trumping others
|
||||
yield kinds.SITE, config_files[kinds.SITE]
|
||||
|
||||
|
|
|
@ -6,9 +6,14 @@ subpackage and, thus, should not depend on them.
|
|||
"""
|
||||
|
||||
import configparser
|
||||
import contextlib
|
||||
import locale
|
||||
import logging
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
from itertools import chain, groupby, repeat
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Union
|
||||
|
||||
from pip._vendor.requests.models import Request, Response
|
||||
from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
|
||||
|
@ -22,6 +27,8 @@ if TYPE_CHECKING:
|
|||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
#
|
||||
# Scaffolding
|
||||
|
@ -658,3 +665,83 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
|
|||
assert self.error is not None
|
||||
message_part = f".\n{self.error}\n"
|
||||
return f"Configuration file {self.reason}{message_part}"
|
||||
|
||||
|
||||
_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\
|
||||
The Python environment under {sys.prefix} is managed externally, and may not be
|
||||
manipulated by the user. Please use specific tooling from the distributor of
|
||||
the Python installation to interact with this environment instead.
|
||||
"""
|
||||
|
||||
|
||||
class ExternallyManagedEnvironment(DiagnosticPipError):
|
||||
"""The current environment is externally managed.
|
||||
|
||||
This is raised when the current environment is externally managed, as
|
||||
defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked
|
||||
and displayed when the error is bubbled up to the user.
|
||||
|
||||
:param error: The error message read from ``EXTERNALLY-MANAGED``.
|
||||
"""
|
||||
|
||||
reference = "externally-managed-environment"
|
||||
|
||||
def __init__(self, error: Optional[str]) -> None:
|
||||
if error is None:
|
||||
context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
|
||||
else:
|
||||
context = Text(error)
|
||||
super().__init__(
|
||||
message="This environment is externally managed",
|
||||
context=context,
|
||||
note_stmt=(
|
||||
"If you believe this is a mistake, please contact your "
|
||||
"Python installation or OS distribution provider. "
|
||||
"You can override this, at the risk of breaking your Python "
|
||||
"installation or OS, by passing --break-system-packages."
|
||||
),
|
||||
hint_stmt=Text("See PEP 668 for the detailed specification."),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _iter_externally_managed_error_keys() -> Iterator[str]:
|
||||
# LC_MESSAGES is in POSIX, but not the C standard. The most common
|
||||
# platform that does not implement this category is Windows, where
|
||||
# using other categories for console message localization is equally
|
||||
# unreliable, so we fall back to the locale-less vendor message. This
|
||||
# can always be re-evaluated when a vendor proposes a new alternative.
|
||||
try:
|
||||
category = locale.LC_MESSAGES
|
||||
except AttributeError:
|
||||
lang: Optional[str] = None
|
||||
else:
|
||||
lang, _ = locale.getlocale(category)
|
||||
if lang is not None:
|
||||
yield f"Error-{lang}"
|
||||
for sep in ("-", "_"):
|
||||
before, found, _ = lang.partition(sep)
|
||||
if not found:
|
||||
continue
|
||||
yield f"Error-{before}"
|
||||
yield "Error"
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
config: Union[pathlib.Path, str],
|
||||
) -> "ExternallyManagedEnvironment":
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
try:
|
||||
parser.read(config, encoding="utf-8")
|
||||
section = parser["externally-managed"]
|
||||
for key in cls._iter_externally_managed_error_keys():
|
||||
with contextlib.suppress(KeyError):
|
||||
return cls(section[key])
|
||||
except KeyError:
|
||||
pass
|
||||
except (OSError, UnicodeDecodeError, configparser.ParsingError):
|
||||
from pip._internal.utils._log import VERBOSE
|
||||
|
||||
exc_info = logger.isEnabledFor(VERBOSE)
|
||||
logger.warning("Failed to read %s", config, exc_info=exc_info)
|
||||
return cls(None)
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
"""Routines related to PyPI, indexes"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import enum
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
from typing import FrozenSet, Iterable, List, Optional, Set, Tuple, Union
|
||||
from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union
|
||||
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
|
@ -39,6 +36,9 @@ from pip._internal.utils.misc import build_netloc
|
|||
from pip._internal.utils.packaging import check_requires_python
|
||||
from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._vendor.typing_extensions import TypeGuard
|
||||
|
||||
__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
|
||||
|
||||
|
||||
|
@ -251,7 +251,7 @@ class LinkEvaluator:
|
|||
|
||||
def filter_unallowed_hashes(
|
||||
candidates: List[InstallationCandidate],
|
||||
hashes: Hashes,
|
||||
hashes: Optional[Hashes],
|
||||
project_name: str,
|
||||
) -> List[InstallationCandidate]:
|
||||
"""
|
||||
|
@ -540,6 +540,7 @@ class CandidateEvaluator:
|
|||
binary_preference = 1
|
||||
if wheel.build_tag is not None:
|
||||
match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
|
||||
assert match is not None, "guaranteed by filename validation"
|
||||
build_tag_groups = match.groups()
|
||||
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
|
||||
else: # sdist
|
||||
|
@ -942,43 +943,46 @@ class PackageFinder:
|
|||
"No matching distribution found for {}".format(req)
|
||||
)
|
||||
|
||||
best_installed = False
|
||||
if installed_version and (
|
||||
best_candidate is None or best_candidate.version <= installed_version
|
||||
):
|
||||
best_installed = True
|
||||
def _should_install_candidate(
|
||||
candidate: Optional[InstallationCandidate],
|
||||
) -> "TypeGuard[InstallationCandidate]":
|
||||
if installed_version is None:
|
||||
return True
|
||||
if best_candidate is None:
|
||||
return False
|
||||
return best_candidate.version > installed_version
|
||||
|
||||
if not upgrade and installed_version is not None:
|
||||
if best_installed:
|
||||
logger.debug(
|
||||
"Existing installed version (%s) is most up-to-date and "
|
||||
"satisfies requirement",
|
||||
installed_version,
|
||||
)
|
||||
else:
|
||||
if _should_install_candidate(best_candidate):
|
||||
logger.debug(
|
||||
"Existing installed version (%s) satisfies requirement "
|
||||
"(most up-to-date version is %s)",
|
||||
installed_version,
|
||||
best_candidate.version,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Existing installed version (%s) is most up-to-date and "
|
||||
"satisfies requirement",
|
||||
installed_version,
|
||||
)
|
||||
return None
|
||||
|
||||
if best_installed:
|
||||
# We have an existing version, and its the best version
|
||||
if _should_install_candidate(best_candidate):
|
||||
logger.debug(
|
||||
"Installed version (%s) is most up-to-date (past versions: %s)",
|
||||
installed_version,
|
||||
"Using version %s (newest of versions: %s)",
|
||||
best_candidate.version,
|
||||
_format_versions(best_candidate_result.iter_applicable()),
|
||||
)
|
||||
raise BestVersionAlreadyInstalled
|
||||
return best_candidate
|
||||
|
||||
# We have an existing version, and its the best version
|
||||
logger.debug(
|
||||
"Using version %s (newest of versions: %s)",
|
||||
best_candidate.version,
|
||||
"Installed version (%s) is most up-to-date (past versions: %s)",
|
||||
installed_version,
|
||||
_format_versions(best_candidate_result.iter_applicable()),
|
||||
)
|
||||
return best_candidate
|
||||
raise BestVersionAlreadyInstalled
|
||||
|
||||
|
||||
def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
|
||||
|
|
|
@ -4,7 +4,7 @@ import os
|
|||
import pathlib
|
||||
import sys
|
||||
import sysconfig
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple
|
||||
from typing import Any, Dict, Generator, Optional, Tuple
|
||||
|
||||
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
|
@ -27,7 +27,6 @@ __all__ = [
|
|||
"get_bin_user",
|
||||
"get_major_minor_version",
|
||||
"get_platlib",
|
||||
"get_isolated_environment_lib_paths",
|
||||
"get_purelib",
|
||||
"get_scheme",
|
||||
"get_src_prefix",
|
||||
|
@ -466,63 +465,3 @@ def get_platlib() -> str:
|
|||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
|
||||
_log_context()
|
||||
return old
|
||||
|
||||
|
||||
def _deduplicated(v1: str, v2: str) -> List[str]:
|
||||
"""Deduplicate values from a list."""
|
||||
if v1 == v2:
|
||||
return [v1]
|
||||
return [v1, v2]
|
||||
|
||||
|
||||
def _looks_like_apple_library(path: str) -> bool:
|
||||
"""Apple patches sysconfig to *always* look under */Library/Python*."""
|
||||
if sys.platform[:6] != "darwin":
|
||||
return False
|
||||
return path == f"/Library/Python/{get_major_minor_version()}/site-packages"
|
||||
|
||||
|
||||
def get_isolated_environment_lib_paths(prefix: str) -> List[str]:
|
||||
"""Return the lib locations under ``prefix``."""
|
||||
new_pure, new_plat = _sysconfig.get_isolated_environment_lib_paths(prefix)
|
||||
if _USE_SYSCONFIG:
|
||||
return _deduplicated(new_pure, new_plat)
|
||||
|
||||
old_pure, old_plat = _distutils.get_isolated_environment_lib_paths(prefix)
|
||||
old_lib_paths = _deduplicated(old_pure, old_plat)
|
||||
|
||||
# Apple's Python (shipped with Xcode and Command Line Tools) hard-code
|
||||
# platlib and purelib to '/Library/Python/X.Y/site-packages'. This will
|
||||
# cause serious build isolation bugs when Apple starts shipping 3.10 because
|
||||
# pip will install build backends to the wrong location. This tells users
|
||||
# who is at fault so Apple may notice it and fix the issue in time.
|
||||
if all(_looks_like_apple_library(p) for p in old_lib_paths):
|
||||
deprecated(
|
||||
reason=(
|
||||
"Python distributed by Apple's Command Line Tools incorrectly "
|
||||
"patches sysconfig to always point to '/Library/Python'. This "
|
||||
"will cause build isolation to operate incorrectly on Python "
|
||||
"3.10 or later. Please help report this to Apple so they can "
|
||||
"fix this. https://developer.apple.com/bug-reporting/"
|
||||
),
|
||||
replacement=None,
|
||||
gone_in=None,
|
||||
)
|
||||
return old_lib_paths
|
||||
|
||||
warned = [
|
||||
_warn_if_mismatch(
|
||||
pathlib.Path(old_pure),
|
||||
pathlib.Path(new_pure),
|
||||
key="prefixed-purelib",
|
||||
),
|
||||
_warn_if_mismatch(
|
||||
pathlib.Path(old_plat),
|
||||
pathlib.Path(new_plat),
|
||||
key="prefixed-platlib",
|
||||
),
|
||||
]
|
||||
if any(warned):
|
||||
_log_context(prefix=prefix)
|
||||
|
||||
return old_lib_paths
|
||||
|
|
|
@ -21,7 +21,7 @@ from distutils.cmd import Command as DistutilsCommand
|
|||
from distutils.command.install import SCHEME_KEYS
|
||||
from distutils.command.install import install as distutils_install_command
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from typing import Dict, List, Optional, Tuple, Union, cast
|
||||
from typing import Dict, List, Optional, Union, cast
|
||||
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
|
@ -171,10 +171,3 @@ def get_purelib() -> str:
|
|||
|
||||
def get_platlib() -> str:
|
||||
return get_python_lib(plat_specific=True)
|
||||
|
||||
|
||||
def get_isolated_environment_lib_paths(prefix: str) -> Tuple[str, str]:
|
||||
return (
|
||||
get_python_lib(plat_specific=False, prefix=prefix),
|
||||
get_python_lib(plat_specific=True, prefix=prefix),
|
||||
)
|
||||
|
|
|
@ -211,12 +211,3 @@ def get_purelib() -> str:
|
|||
|
||||
def get_platlib() -> str:
|
||||
return sysconfig.get_paths()["platlib"]
|
||||
|
||||
|
||||
def get_isolated_environment_lib_paths(prefix: str) -> typing.Tuple[str, str]:
|
||||
vars = {"base": prefix, "platbase": prefix}
|
||||
if "venv" in sysconfig.get_scheme_names():
|
||||
paths = sysconfig.get_paths(vars=vars, scheme="venv")
|
||||
else:
|
||||
paths = sysconfig.get_paths(vars=vars)
|
||||
return (paths["purelib"], paths["platlib"])
|
||||
|
|
|
@ -13,7 +13,7 @@ from pip._internal.utils.virtualenv import running_under_virtualenv
|
|||
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
|
||||
|
||||
# FIXME doesn't account for venv linked to global site-packages
|
||||
site_packages: typing.Optional[str] = sysconfig.get_path("purelib")
|
||||
site_packages: str = sysconfig.get_path("purelib")
|
||||
|
||||
|
||||
def get_major_minor_version() -> str:
|
||||
|
|
|
@ -103,17 +103,33 @@ class ArchiveInfo:
|
|||
def __init__(
|
||||
self,
|
||||
hash: Optional[str] = None,
|
||||
hashes: Optional[Dict[str, str]] = None,
|
||||
) -> None:
|
||||
if hash is not None:
|
||||
# Auto-populate the hashes key to upgrade to the new format automatically.
|
||||
# We don't back-populate the legacy hash key.
|
||||
try:
|
||||
hash_name, hash_value = hash.split("=", 1)
|
||||
except ValueError:
|
||||
raise DirectUrlValidationError(
|
||||
f"invalid archive_info.hash format: {hash!r}"
|
||||
)
|
||||
if hashes is None:
|
||||
hashes = {hash_name: hash_value}
|
||||
elif hash_name not in hash:
|
||||
hashes = hashes.copy()
|
||||
hashes[hash_name] = hash_value
|
||||
self.hash = hash
|
||||
self.hashes = hashes
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
|
||||
if d is None:
|
||||
return None
|
||||
return cls(hash=_get(d, str, "hash"))
|
||||
return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
|
||||
|
||||
def _to_dict(self) -> Dict[str, Any]:
|
||||
return _filter_none(hash=self.hash)
|
||||
return _filter_none(hash=self.hash, hashes=self.hashes)
|
||||
|
||||
|
||||
class DirInfo:
|
||||
|
|
|
@ -38,7 +38,7 @@ class InstallationReport:
|
|||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"version": "0",
|
||||
"version": "1",
|
||||
"pip_version": __version__,
|
||||
"install": [
|
||||
self._install_req_to_dict(ireq) for ireq in self._install_requirements
|
||||
|
|
|
@ -18,6 +18,7 @@ from typing import (
|
|||
Union,
|
||||
)
|
||||
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
from pip._internal.utils.filetypes import WHEEL_EXTENSION
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.misc import (
|
||||
|
@ -78,6 +79,9 @@ class LinkHash:
|
|||
name, value = match.groups()
|
||||
return cls(name=name, value=value)
|
||||
|
||||
def as_dict(self) -> Dict[str, str]:
|
||||
return {self.name: self.value}
|
||||
|
||||
def as_hashes(self) -> Hashes:
|
||||
"""Return a Hashes instance which checks only for the current hash."""
|
||||
return Hashes({self.name: [self.value]})
|
||||
|
@ -164,8 +168,8 @@ class Link(KeyBasedCompareMixin):
|
|||
"requires_python",
|
||||
"yanked_reason",
|
||||
"dist_info_metadata",
|
||||
"link_hash",
|
||||
"cache_link_parsing",
|
||||
"egg_fragment",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
|
@ -175,7 +179,6 @@ class Link(KeyBasedCompareMixin):
|
|||
requires_python: Optional[str] = None,
|
||||
yanked_reason: Optional[str] = None,
|
||||
dist_info_metadata: Optional[str] = None,
|
||||
link_hash: Optional[LinkHash] = None,
|
||||
cache_link_parsing: bool = True,
|
||||
hashes: Optional[Mapping[str, str]] = None,
|
||||
) -> None:
|
||||
|
@ -198,16 +201,11 @@ class Link(KeyBasedCompareMixin):
|
|||
attribute, if present, in a simple repository HTML link. This may be parsed
|
||||
into its own `Link` by `self.metadata_link()`. See PEP 658 for more
|
||||
information and the specification.
|
||||
:param link_hash: a checksum for the content the link points to. If not
|
||||
provided, this will be extracted from the link URL, if the URL has
|
||||
any checksum.
|
||||
:param cache_link_parsing: A flag that is used elsewhere to determine
|
||||
whether resources retrieved from this link
|
||||
should be cached. PyPI index urls should
|
||||
generally have this set to False, for
|
||||
example.
|
||||
whether resources retrieved from this link should be cached. PyPI
|
||||
URLs should generally have this set to False, for example.
|
||||
:param hashes: A mapping of hash names to digests to allow us to
|
||||
determine the validity of a download.
|
||||
determine the validity of a download.
|
||||
"""
|
||||
|
||||
# url can be a UNC windows share
|
||||
|
@ -218,17 +216,23 @@ class Link(KeyBasedCompareMixin):
|
|||
# Store the url as a private attribute to prevent accidentally
|
||||
# trying to set a new value.
|
||||
self._url = url
|
||||
self._hashes = hashes if hashes is not None else {}
|
||||
|
||||
link_hash = LinkHash.split_hash_name_and_value(url)
|
||||
hashes_from_link = {} if link_hash is None else link_hash.as_dict()
|
||||
if hashes is None:
|
||||
self._hashes = hashes_from_link
|
||||
else:
|
||||
self._hashes = {**hashes, **hashes_from_link}
|
||||
|
||||
self.comes_from = comes_from
|
||||
self.requires_python = requires_python if requires_python else None
|
||||
self.yanked_reason = yanked_reason
|
||||
self.dist_info_metadata = dist_info_metadata
|
||||
self.link_hash = link_hash or LinkHash.split_hash_name_and_value(self._url)
|
||||
|
||||
super().__init__(key=url, defining_class=Link)
|
||||
|
||||
self.cache_link_parsing = cache_link_parsing
|
||||
self.egg_fragment = self._egg_fragment()
|
||||
|
||||
@classmethod
|
||||
def from_json(
|
||||
|
@ -358,12 +362,28 @@ class Link(KeyBasedCompareMixin):
|
|||
|
||||
_egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
|
||||
|
||||
@property
|
||||
def egg_fragment(self) -> Optional[str]:
|
||||
# Per PEP 508.
|
||||
_project_name_re = re.compile(
|
||||
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
|
||||
)
|
||||
|
||||
def _egg_fragment(self) -> Optional[str]:
|
||||
match = self._egg_fragment_re.search(self._url)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1)
|
||||
|
||||
# An egg fragment looks like a PEP 508 project name, along with
|
||||
# an optional extras specifier. Anything else is invalid.
|
||||
project_name = match.group(1)
|
||||
if not self._project_name_re.match(project_name):
|
||||
deprecated(
|
||||
reason=f"{self} contains an egg fragment with a non-PEP 508 name",
|
||||
replacement="to use the req @ url syntax, and remove the egg fragment",
|
||||
gone_in="25.0",
|
||||
issue=11617,
|
||||
)
|
||||
|
||||
return project_name
|
||||
|
||||
_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
|
||||
|
||||
|
@ -382,29 +402,26 @@ class Link(KeyBasedCompareMixin):
|
|||
if self.dist_info_metadata is None:
|
||||
return None
|
||||
metadata_url = f"{self.url_without_fragment}.metadata"
|
||||
link_hash: Optional[LinkHash] = None
|
||||
# If data-dist-info-metadata="true" is set, then the metadata file exists,
|
||||
# but there is no information about its checksum or anything else.
|
||||
if self.dist_info_metadata != "true":
|
||||
link_hash = LinkHash.split_hash_name_and_value(self.dist_info_metadata)
|
||||
return Link(metadata_url, link_hash=link_hash)
|
||||
else:
|
||||
link_hash = None
|
||||
if link_hash is None:
|
||||
return Link(metadata_url)
|
||||
return Link(metadata_url, hashes=link_hash.as_dict())
|
||||
|
||||
def as_hashes(self) -> Optional[Hashes]:
|
||||
if self.link_hash is not None:
|
||||
return self.link_hash.as_hashes()
|
||||
return None
|
||||
def as_hashes(self) -> Hashes:
|
||||
return Hashes({k: [v] for k, v in self._hashes.items()})
|
||||
|
||||
@property
|
||||
def hash(self) -> Optional[str]:
|
||||
if self.link_hash is not None:
|
||||
return self.link_hash.value
|
||||
return None
|
||||
return next(iter(self._hashes.values()), None)
|
||||
|
||||
@property
|
||||
def hash_name(self) -> Optional[str]:
|
||||
if self.link_hash is not None:
|
||||
return self.link_hash.name
|
||||
return None
|
||||
return next(iter(self._hashes), None)
|
||||
|
||||
@property
|
||||
def show_url(self) -> str:
|
||||
|
@ -433,15 +450,15 @@ class Link(KeyBasedCompareMixin):
|
|||
|
||||
@property
|
||||
def has_hash(self) -> bool:
|
||||
return self.link_hash is not None
|
||||
return bool(self._hashes)
|
||||
|
||||
def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
|
||||
"""
|
||||
Return True if the link has a hash and it is allowed by `hashes`.
|
||||
"""
|
||||
if self.link_hash is None:
|
||||
if hashes is None:
|
||||
return False
|
||||
return self.link_hash.is_hash_allowed(hashes)
|
||||
return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
|
||||
|
||||
|
||||
class _CleanResult(NamedTuple):
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
Contains interface (MultiDomainBasicAuth) and associated glue code for
|
||||
providing credentials in the context of network requests.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sysconfig
|
||||
import typing
|
||||
import urllib.parse
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache
|
||||
from os.path import commonprefix
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
|
||||
|
||||
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||
|
@ -39,6 +44,8 @@ class Credentials(NamedTuple):
|
|||
class KeyRingBaseProvider(ABC):
|
||||
"""Keyring base provider interface"""
|
||||
|
||||
has_keyring: bool
|
||||
|
||||
@abstractmethod
|
||||
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
|
||||
...
|
||||
|
@ -51,6 +58,8 @@ class KeyRingBaseProvider(ABC):
|
|||
class KeyRingNullProvider(KeyRingBaseProvider):
|
||||
"""Keyring null provider"""
|
||||
|
||||
has_keyring = False
|
||||
|
||||
def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
|
||||
return None
|
||||
|
||||
|
@ -61,6 +70,8 @@ class KeyRingNullProvider(KeyRingBaseProvider):
|
|||
class KeyRingPythonProvider(KeyRingBaseProvider):
|
||||
"""Keyring interface which uses locally imported `keyring`"""
|
||||
|
||||
has_keyring = True
|
||||
|
||||
def __init__(self) -> None:
|
||||
import keyring
|
||||
|
||||
|
@ -97,6 +108,8 @@ class KeyRingCliProvider(KeyRingBaseProvider):
|
|||
PATH.
|
||||
"""
|
||||
|
||||
has_keyring = True
|
||||
|
||||
def __init__(self, cmd: str) -> None:
|
||||
self.keyring = cmd
|
||||
|
||||
|
@ -123,77 +136,100 @@ class KeyRingCliProvider(KeyRingBaseProvider):
|
|||
res = subprocess.run(
|
||||
cmd,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
stdout=subprocess.PIPE,
|
||||
env=env,
|
||||
)
|
||||
if res.returncode:
|
||||
return None
|
||||
return res.stdout.decode("utf-8").strip("\n")
|
||||
return res.stdout.decode("utf-8").strip(os.linesep)
|
||||
|
||||
def _set_password(self, service_name: str, username: str, password: str) -> None:
|
||||
"""Mirror the implementation of keyring.set_password using cli"""
|
||||
if self.keyring is None:
|
||||
return None
|
||||
|
||||
cmd = [self.keyring, "set", service_name, username]
|
||||
input_ = password.encode("utf-8") + b"\n"
|
||||
env = os.environ.copy()
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
res = subprocess.run(cmd, input=input_, env=env)
|
||||
res.check_returncode()
|
||||
subprocess.run(
|
||||
[self.keyring, "set", service_name, username],
|
||||
input=f"{password}{os.linesep}".encode("utf-8"),
|
||||
env=env,
|
||||
check=True,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def get_keyring_provider() -> KeyRingBaseProvider:
|
||||
@lru_cache(maxsize=None)
|
||||
def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
|
||||
logger.verbose("Keyring provider requested: %s", provider)
|
||||
|
||||
# keyring has previously failed and been disabled
|
||||
if not KEYRING_DISABLED:
|
||||
# Default to trying to use Python provider
|
||||
if KEYRING_DISABLED:
|
||||
provider = "disabled"
|
||||
if provider in ["import", "auto"]:
|
||||
try:
|
||||
return KeyRingPythonProvider()
|
||||
impl = KeyRingPythonProvider()
|
||||
logger.verbose("Keyring provider set: import")
|
||||
return impl
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# In the event of an unexpected exception
|
||||
# we should warn the user
|
||||
logger.warning(
|
||||
"Installed copy of keyring fails with exception %s, "
|
||||
"trying to find a keyring executable as a fallback",
|
||||
str(exc),
|
||||
)
|
||||
|
||||
# Fallback to Cli Provider if `keyring` isn't installed
|
||||
msg = "Installed copy of keyring fails with exception %s"
|
||||
if provider == "auto":
|
||||
msg = msg + ", trying to find a keyring executable as a fallback"
|
||||
logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
|
||||
if provider in ["subprocess", "auto"]:
|
||||
cli = shutil.which("keyring")
|
||||
if cli and cli.startswith(sysconfig.get_path("scripts")):
|
||||
# all code within this function is stolen from shutil.which implementation
|
||||
@typing.no_type_check
|
||||
def PATH_as_shutil_which_determines_it() -> str:
|
||||
path = os.environ.get("PATH", None)
|
||||
if path is None:
|
||||
try:
|
||||
path = os.confstr("CS_PATH")
|
||||
except (AttributeError, ValueError):
|
||||
# os.confstr() or CS_PATH is not available
|
||||
path = os.defpath
|
||||
# bpo-35755: Don't use os.defpath if the PATH environment variable is
|
||||
# set to an empty string
|
||||
|
||||
return path
|
||||
|
||||
scripts = Path(sysconfig.get_path("scripts"))
|
||||
|
||||
paths = []
|
||||
for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
|
||||
p = Path(path)
|
||||
try:
|
||||
if not p.samefile(scripts):
|
||||
paths.append(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
path = os.pathsep.join(paths)
|
||||
|
||||
cli = shutil.which("keyring", path=path)
|
||||
|
||||
if cli:
|
||||
logger.verbose("Keyring provider set: subprocess with executable %s", cli)
|
||||
return KeyRingCliProvider(cli)
|
||||
|
||||
logger.verbose("Keyring provider set: disabled")
|
||||
return KeyRingNullProvider()
|
||||
|
||||
|
||||
def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
|
||||
"""Return the tuple auth for a given url from keyring."""
|
||||
# Do nothing if no url was provided
|
||||
if not url:
|
||||
return None
|
||||
|
||||
keyring = get_keyring_provider()
|
||||
try:
|
||||
return keyring.get_auth_info(url, username)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Keyring is skipped due to an exception: %s",
|
||||
str(exc),
|
||||
)
|
||||
global KEYRING_DISABLED
|
||||
KEYRING_DISABLED = True
|
||||
return None
|
||||
|
||||
|
||||
class MultiDomainBasicAuth(AuthBase):
|
||||
def __init__(
|
||||
self, prompting: bool = True, index_urls: Optional[List[str]] = None
|
||||
self,
|
||||
prompting: bool = True,
|
||||
index_urls: Optional[List[str]] = None,
|
||||
keyring_provider: str = "auto",
|
||||
) -> None:
|
||||
self.prompting = prompting
|
||||
self.index_urls = index_urls
|
||||
self.keyring_provider = keyring_provider # type: ignore[assignment]
|
||||
self.passwords: Dict[str, AuthInfo] = {}
|
||||
# When the user is prompted to enter credentials and keyring is
|
||||
# available, we will offer to save them. If the user accepts,
|
||||
|
@ -202,6 +238,47 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
# ``save_credentials`` to save these.
|
||||
self._credentials_to_save: Optional[Credentials] = None
|
||||
|
||||
@property
|
||||
def keyring_provider(self) -> KeyRingBaseProvider:
|
||||
return get_keyring_provider(self._keyring_provider)
|
||||
|
||||
@keyring_provider.setter
|
||||
def keyring_provider(self, provider: str) -> None:
|
||||
# The free function get_keyring_provider has been decorated with
|
||||
# functools.cache. If an exception occurs in get_keyring_auth that
|
||||
# cache will be cleared and keyring disabled, take that into account
|
||||
# if you want to remove this indirection.
|
||||
self._keyring_provider = provider
|
||||
|
||||
@property
|
||||
def use_keyring(self) -> bool:
|
||||
# We won't use keyring when --no-input is passed unless
|
||||
# a specific provider is requested because it might require
|
||||
# user interaction
|
||||
return self.prompting or self._keyring_provider not in ["auto", "disabled"]
|
||||
|
||||
def _get_keyring_auth(
|
||||
self,
|
||||
url: Optional[str],
|
||||
username: Optional[str],
|
||||
) -> Optional[AuthInfo]:
|
||||
"""Return the tuple auth for a given url from keyring."""
|
||||
# Do nothing if no url was provided
|
||||
if not url:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.keyring_provider.get_auth_info(url, username)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Keyring is skipped due to an exception: %s",
|
||||
str(exc),
|
||||
)
|
||||
global KEYRING_DISABLED
|
||||
KEYRING_DISABLED = True
|
||||
get_keyring_provider.cache_clear()
|
||||
return None
|
||||
|
||||
def _get_index_url(self, url: str) -> Optional[str]:
|
||||
"""Return the original index URL matching the requested URL.
|
||||
|
||||
|
@ -218,15 +295,42 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
if not url or not self.index_urls:
|
||||
return None
|
||||
|
||||
for u in self.index_urls:
|
||||
prefix = remove_auth_from_url(u).rstrip("/") + "/"
|
||||
if url.startswith(prefix):
|
||||
return u
|
||||
return None
|
||||
url = remove_auth_from_url(url).rstrip("/") + "/"
|
||||
parsed_url = urllib.parse.urlsplit(url)
|
||||
|
||||
candidates = []
|
||||
|
||||
for index in self.index_urls:
|
||||
index = index.rstrip("/") + "/"
|
||||
parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
|
||||
if parsed_url == parsed_index:
|
||||
return index
|
||||
|
||||
if parsed_url.netloc != parsed_index.netloc:
|
||||
continue
|
||||
|
||||
candidate = urllib.parse.urlsplit(index)
|
||||
candidates.append(candidate)
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
candidates.sort(
|
||||
reverse=True,
|
||||
key=lambda candidate: commonprefix(
|
||||
[
|
||||
parsed_url.path,
|
||||
candidate.path,
|
||||
]
|
||||
).rfind("/"),
|
||||
)
|
||||
|
||||
return urllib.parse.urlunsplit(candidates[0])
|
||||
|
||||
def _get_new_credentials(
|
||||
self,
|
||||
original_url: str,
|
||||
*,
|
||||
allow_netrc: bool = True,
|
||||
allow_keyring: bool = False,
|
||||
) -> AuthInfo:
|
||||
|
@ -270,8 +374,8 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
# The index url is more specific than the netloc, so try it first
|
||||
# fmt: off
|
||||
kr_auth = (
|
||||
get_keyring_auth(index_url, username) or
|
||||
get_keyring_auth(netloc, username)
|
||||
self._get_keyring_auth(index_url, username) or
|
||||
self._get_keyring_auth(netloc, username)
|
||||
)
|
||||
# fmt: on
|
||||
if kr_auth:
|
||||
|
@ -348,18 +452,23 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
def _prompt_for_password(
|
||||
self, netloc: str
|
||||
) -> Tuple[Optional[str], Optional[str], bool]:
|
||||
username = ask_input(f"User for {netloc}: ")
|
||||
username = ask_input(f"User for {netloc}: ") if self.prompting else None
|
||||
if not username:
|
||||
return None, None, False
|
||||
auth = get_keyring_auth(netloc, username)
|
||||
if auth and auth[0] is not None and auth[1] is not None:
|
||||
return auth[0], auth[1], False
|
||||
if self.use_keyring:
|
||||
auth = self._get_keyring_auth(netloc, username)
|
||||
if auth and auth[0] is not None and auth[1] is not None:
|
||||
return auth[0], auth[1], False
|
||||
password = ask_password("Password: ")
|
||||
return username, password, True
|
||||
|
||||
# Factored out to allow for easy patching in tests
|
||||
def _should_save_password_to_keyring(self) -> bool:
|
||||
if get_keyring_provider() is None:
|
||||
if (
|
||||
not self.prompting
|
||||
or not self.use_keyring
|
||||
or not self.keyring_provider.has_keyring
|
||||
):
|
||||
return False
|
||||
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
|
||||
|
||||
|
@ -369,19 +478,22 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
if resp.status_code != 401:
|
||||
return resp
|
||||
|
||||
username, password = None, None
|
||||
|
||||
# Query the keyring for credentials:
|
||||
if self.use_keyring:
|
||||
username, password = self._get_new_credentials(
|
||||
resp.url,
|
||||
allow_netrc=False,
|
||||
allow_keyring=True,
|
||||
)
|
||||
|
||||
# We are not able to prompt the user so simply return the response
|
||||
if not self.prompting:
|
||||
if not self.prompting and not username and not password:
|
||||
return resp
|
||||
|
||||
parsed = urllib.parse.urlparse(resp.url)
|
||||
|
||||
# Query the keyring for credentials:
|
||||
username, password = self._get_new_credentials(
|
||||
resp.url,
|
||||
allow_netrc=False,
|
||||
allow_keyring=True,
|
||||
)
|
||||
|
||||
# Prompt the user for a new username and password
|
||||
save = False
|
||||
if not username and not password:
|
||||
|
@ -431,9 +543,8 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
|
||||
def save_credentials(self, resp: Response, **kwargs: Any) -> None:
|
||||
"""Response callback to save credentials on success."""
|
||||
keyring = get_keyring_provider()
|
||||
assert not isinstance(
|
||||
keyring, KeyRingNullProvider
|
||||
assert (
|
||||
self.keyring_provider.has_keyring
|
||||
), "should never reach here without keyring"
|
||||
|
||||
creds = self._credentials_to_save
|
||||
|
@ -441,6 +552,8 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
if creds and resp.status_code < 400:
|
||||
try:
|
||||
logger.info("Saving credentials to keyring")
|
||||
keyring.save_auth_info(creds.url, creds.username, creds.password)
|
||||
self.keyring_provider.save_auth_info(
|
||||
creds.url, creds.username, creds.password
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to save credentials")
|
||||
|
|
|
@ -6,7 +6,7 @@ from bisect import bisect_left, bisect_right
|
|||
from contextlib import contextmanager
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple
|
||||
from zipfile import BadZipfile, ZipFile
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
|
@ -160,7 +160,7 @@ class LazyZipOverHTTP:
|
|||
# For read-only ZIP files, ZipFile only needs
|
||||
# methods read, seek, seekable and tell.
|
||||
ZipFile(self) # type: ignore
|
||||
except BadZipfile:
|
||||
except BadZipFile:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import os
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
|
@ -15,7 +15,7 @@ from pip._internal.utils.temp_dir import TempDirectory
|
|||
|
||||
|
||||
def generate_metadata(
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
|
||||
build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 517.
|
||||
|
||||
|
@ -26,7 +26,7 @@ def generate_metadata(
|
|||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
with build_env:
|
||||
# Note that Pep517HookCaller implements a fallback for
|
||||
# Note that BuildBackendHookCaller implements a fallback for
|
||||
# prepare_metadata_for_build_wheel, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import os
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
|
@ -15,7 +15,7 @@ from pip._internal.utils.temp_dir import TempDirectory
|
|||
|
||||
|
||||
def generate_editable_metadata(
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
|
||||
build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 660.
|
||||
|
||||
|
@ -26,7 +26,7 @@ def generate_editable_metadata(
|
|||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
with build_env:
|
||||
# Note that Pep517HookCaller implements a fallback for
|
||||
# Note that BuildBackendHookCaller implements a fallback for
|
||||
# prepare_metadata_for_build_wheel/editable, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message(
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
|
||||
|
@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def build_wheel_pep517(
|
||||
name: str,
|
||||
backend: Pep517HookCaller,
|
||||
backend: BuildBackendHookCaller,
|
||||
metadata_directory: str,
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
|
||||
|
@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def build_wheel_editable(
|
||||
name: str,
|
||||
backend: Pep517HookCaller,
|
||||
backend: BuildBackendHookCaller,
|
||||
metadata_directory: str,
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
|
|
|
@ -75,7 +75,7 @@ def check_package_set(
|
|||
if name not in package_set:
|
||||
missed = True
|
||||
if req.marker is not None:
|
||||
missed = req.marker.evaluate()
|
||||
missed = req.marker.evaluate({"extra": ""})
|
||||
if missed:
|
||||
missing_deps.add((name, req))
|
||||
continue
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Legacy editable installation process, i.e. `setup.py develop`.
|
||||
"""
|
||||
import logging
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.utils.logging import indent_log
|
||||
|
@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def install_editable(
|
||||
install_options: List[str],
|
||||
*,
|
||||
global_options: Sequence[str],
|
||||
prefix: Optional[str],
|
||||
home: Optional[str],
|
||||
|
@ -31,7 +31,6 @@ def install_editable(
|
|||
args = make_setuptools_develop_args(
|
||||
setup_py_path,
|
||||
global_options=global_options,
|
||||
install_options=install_options,
|
||||
no_user_config=isolated,
|
||||
prefix=prefix,
|
||||
home=home,
|
||||
|
|
|
@ -55,7 +55,6 @@ def write_installed_files_from_setuptools_record(
|
|||
|
||||
|
||||
def install(
|
||||
install_options: List[str],
|
||||
global_options: Sequence[str],
|
||||
root: Optional[str],
|
||||
home: Optional[str],
|
||||
|
@ -79,7 +78,6 @@ def install(
|
|||
install_args = make_setuptools_install_args(
|
||||
setup_py_path,
|
||||
global_options=global_options,
|
||||
install_options=install_options,
|
||||
record_filename=record_filename,
|
||||
root=root,
|
||||
prefix=prefix,
|
||||
|
|
|
@ -270,6 +270,16 @@ class RequirementPreparer:
|
|||
message = "Collecting %s"
|
||||
information = str(req.req or req)
|
||||
|
||||
# If we used req.req, inject requirement source if available (this
|
||||
# would already be included if we used req directly)
|
||||
if req.req and req.comes_from:
|
||||
if isinstance(req.comes_from, str):
|
||||
comes_from: Optional[str] = req.comes_from
|
||||
else:
|
||||
comes_from = req.comes_from.from_path()
|
||||
if comes_from:
|
||||
information += f" (from {comes_from})"
|
||||
|
||||
if (message, information) != self._previous_requirement_header:
|
||||
self._previous_requirement_header = (message, information)
|
||||
logger.info(message, information)
|
||||
|
|
|
@ -159,9 +159,8 @@ def load_pyproject_toml(
|
|||
if backend is None:
|
||||
# If the user didn't specify a backend, we assume they want to use
|
||||
# the setuptools backend. But we can't be sure they have included
|
||||
# a version of setuptools which supplies the backend, or wheel
|
||||
# (which is needed by the backend) in their requirements. So we
|
||||
# make a note to check that those requirements are present once
|
||||
# a version of setuptools which supplies the backend. So we
|
||||
# make a note to check that this requirement is present once
|
||||
# we have set up the environment.
|
||||
# This is quite a lot of work to check for a very specific case. But
|
||||
# the problem is, that case is potentially quite common - projects that
|
||||
|
@ -170,6 +169,6 @@ def load_pyproject_toml(
|
|||
# tools themselves. The original PEP 518 code had a similar check (but
|
||||
# implemented in a different way).
|
||||
backend = "setuptools.build_meta:__legacy__"
|
||||
check = ["setuptools>=40.8.0", "wheel"]
|
||||
check = ["setuptools>=40.8.0"]
|
||||
|
||||
return BuildSystemDetails(requires, backend, check, backend_path)
|
||||
|
|
|
@ -36,7 +36,6 @@ def _validate_requirements(
|
|||
|
||||
def install_given_reqs(
|
||||
requirements: List[InstallRequirement],
|
||||
install_options: List[str],
|
||||
global_options: Sequence[str],
|
||||
root: Optional[str],
|
||||
home: Optional[str],
|
||||
|
@ -71,7 +70,6 @@ def install_given_reqs(
|
|||
|
||||
try:
|
||||
requirement.install(
|
||||
install_options,
|
||||
global_options,
|
||||
root=root,
|
||||
home=home,
|
||||
|
|
|
@ -222,7 +222,6 @@ def install_req_from_editable(
|
|||
constraint=constraint,
|
||||
use_pep517=use_pep517,
|
||||
isolated=isolated,
|
||||
install_options=options.get("install_options", []) if options else [],
|
||||
global_options=options.get("global_options", []) if options else [],
|
||||
hash_options=options.get("hashes", {}) if options else {},
|
||||
config_settings=config_settings,
|
||||
|
@ -399,7 +398,6 @@ def install_req_from_line(
|
|||
markers=parts.markers,
|
||||
use_pep517=use_pep517,
|
||||
isolated=isolated,
|
||||
install_options=options.get("install_options", []) if options else [],
|
||||
global_options=options.get("global_options", []) if options else [],
|
||||
hash_options=options.get("hashes", {}) if options else {},
|
||||
config_settings=config_settings,
|
||||
|
@ -493,7 +491,6 @@ def install_req_from_link_and_ireq(
|
|||
markers=ireq.markers,
|
||||
use_pep517=ireq.use_pep517,
|
||||
isolated=ireq.isolated,
|
||||
install_options=ireq.install_options,
|
||||
global_options=ireq.global_options,
|
||||
hash_options=ireq.hash_options,
|
||||
config_settings=ireq.config_settings,
|
||||
|
|
|
@ -69,7 +69,6 @@ SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
|
|||
|
||||
# options to be passed to requirements
|
||||
SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
|
||||
cmdoptions.install_options,
|
||||
cmdoptions.global_options,
|
||||
cmdoptions.hash,
|
||||
cmdoptions.config_settings,
|
||||
|
|
|
@ -8,7 +8,6 @@ import shutil
|
|||
import sys
|
||||
import uuid
|
||||
import zipfile
|
||||
from enum import Enum
|
||||
from optparse import Values
|
||||
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
|
||||
|
||||
|
@ -18,7 +17,7 @@ from pip._vendor.packaging.specifiers import SpecifierSet
|
|||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
|
||||
from pip._internal.exceptions import InstallationError, LegacyInstallFailure
|
||||
|
@ -51,7 +50,7 @@ from pip._internal.utils.direct_url_helpers import (
|
|||
)
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.misc import (
|
||||
ConfiguredPep517HookCaller,
|
||||
ConfiguredBuildBackendHookCaller,
|
||||
ask_path_exists,
|
||||
backup_dir,
|
||||
display_path,
|
||||
|
@ -83,7 +82,7 @@ class InstallRequirement:
|
|||
markers: Optional[Marker] = None,
|
||||
use_pep517: Optional[bool] = None,
|
||||
isolated: bool = False,
|
||||
install_options: Optional[List[str]] = None,
|
||||
*,
|
||||
global_options: Optional[List[str]] = None,
|
||||
hash_options: Optional[Dict[str, List[str]]] = None,
|
||||
config_settings: Optional[Dict[str, str]] = None,
|
||||
|
@ -146,7 +145,6 @@ class InstallRequirement:
|
|||
# Set to True after successful installation
|
||||
self.install_succeeded: Optional[bool] = None
|
||||
# Supplied options
|
||||
self.install_options = install_options if install_options else []
|
||||
self.global_options = global_options if global_options else []
|
||||
self.hash_options = hash_options if hash_options else {}
|
||||
self.config_settings = config_settings
|
||||
|
@ -182,7 +180,7 @@ class InstallRequirement:
|
|||
self.requirements_to_check: List[str] = []
|
||||
|
||||
# The PEP 517 backend we should use to build the project
|
||||
self.pep517_backend: Optional[Pep517HookCaller] = None
|
||||
self.pep517_backend: Optional[BuildBackendHookCaller] = None
|
||||
|
||||
# Are we using PEP 517 for this requirement?
|
||||
# After pyproject.toml has been loaded, the only valid values are True
|
||||
|
@ -204,7 +202,11 @@ class InstallRequirement:
|
|||
else:
|
||||
s = "<InstallRequirement>"
|
||||
if self.satisfied_by is not None:
|
||||
s += " in {}".format(display_path(self.satisfied_by.location))
|
||||
if self.satisfied_by.location is not None:
|
||||
location = display_path(self.satisfied_by.location)
|
||||
else:
|
||||
location = "<memory>"
|
||||
s += f" in {location}"
|
||||
if self.comes_from:
|
||||
if isinstance(self.comes_from, str):
|
||||
comes_from: Optional[str] = self.comes_from
|
||||
|
@ -491,7 +493,7 @@ class InstallRequirement:
|
|||
requires, backend, check, backend_path = pyproject_toml_data
|
||||
self.requirements_to_check = check
|
||||
self.pyproject_requires = requires
|
||||
self.pep517_backend = ConfiguredPep517HookCaller(
|
||||
self.pep517_backend = ConfiguredBuildBackendHookCaller(
|
||||
self,
|
||||
self.unpacked_source_directory,
|
||||
backend,
|
||||
|
@ -751,7 +753,6 @@ class InstallRequirement:
|
|||
|
||||
def install(
|
||||
self,
|
||||
install_options: List[str],
|
||||
global_options: Optional[Sequence[str]] = None,
|
||||
root: Optional[str] = None,
|
||||
home: Optional[str] = None,
|
||||
|
@ -772,8 +773,7 @@ class InstallRequirement:
|
|||
global_options = global_options if global_options is not None else []
|
||||
if self.editable and not self.is_wheel:
|
||||
install_editable_legacy(
|
||||
install_options,
|
||||
global_options,
|
||||
global_options=global_options,
|
||||
prefix=prefix,
|
||||
home=home,
|
||||
use_user_site=use_user_site,
|
||||
|
@ -813,13 +813,12 @@ class InstallRequirement:
|
|||
|
||||
# TODO: Why don't we do this for editable installs?
|
||||
|
||||
# Extend the list of global and install options passed on to
|
||||
# Extend the list of global options passed on to
|
||||
# the setup.py call with the ones from the requirements file.
|
||||
# Options specified in requirements file override those
|
||||
# specified on the command line, since the last option given
|
||||
# to setup.py is the one that is used.
|
||||
global_options = list(global_options) + self.global_options
|
||||
install_options = list(install_options) + self.install_options
|
||||
|
||||
try:
|
||||
if (
|
||||
|
@ -828,7 +827,6 @@ class InstallRequirement:
|
|||
):
|
||||
self.legacy_install_reason.emit_deprecation(self.name)
|
||||
success = install_legacy(
|
||||
install_options=install_options,
|
||||
global_options=global_options,
|
||||
root=root,
|
||||
home=home,
|
||||
|
@ -898,54 +896,16 @@ def _has_option(options: Values, reqs: List[InstallRequirement], option: str) ->
|
|||
return False
|
||||
|
||||
|
||||
def _install_option_ignored(
|
||||
install_options: List[str], reqs: List[InstallRequirement]
|
||||
) -> bool:
|
||||
for req in reqs:
|
||||
if (install_options or req.install_options) and not req.use_pep517:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class LegacySetupPyOptionsCheckMode(Enum):
|
||||
INSTALL = 1
|
||||
WHEEL = 2
|
||||
DOWNLOAD = 3
|
||||
|
||||
|
||||
def check_legacy_setup_py_options(
|
||||
options: Values,
|
||||
reqs: List[InstallRequirement],
|
||||
mode: LegacySetupPyOptionsCheckMode,
|
||||
) -> None:
|
||||
has_install_options = _has_option(options, reqs, "install_options")
|
||||
has_build_options = _has_option(options, reqs, "build_options")
|
||||
has_global_options = _has_option(options, reqs, "global_options")
|
||||
legacy_setup_py_options_present = (
|
||||
has_install_options or has_build_options or has_global_options
|
||||
)
|
||||
if not legacy_setup_py_options_present:
|
||||
return
|
||||
|
||||
options.format_control.disallow_binaries()
|
||||
logger.warning(
|
||||
"Implying --no-binary=:all: due to the presence of "
|
||||
"--build-option / --global-option / --install-option. "
|
||||
"Consider using --config-settings for more flexibility.",
|
||||
)
|
||||
if mode == LegacySetupPyOptionsCheckMode.INSTALL and has_install_options:
|
||||
if _install_option_ignored(options.install_options, reqs):
|
||||
logger.warning(
|
||||
"Ignoring --install-option when building using PEP 517",
|
||||
)
|
||||
else:
|
||||
deprecated(
|
||||
reason=(
|
||||
"--install-option is deprecated because "
|
||||
"it forces pip to use the 'setup.py install' "
|
||||
"command which is itself deprecated."
|
||||
),
|
||||
issue=11358,
|
||||
replacement="to use --config-settings",
|
||||
gone_in="23.1",
|
||||
)
|
||||
if has_build_options or has_global_options:
|
||||
logger.warning(
|
||||
"Implying --no-binary=:all: due to the presence of "
|
||||
"--build-option / --global-option. "
|
||||
"Consider using --config-settings for more flexibility.",
|
||||
)
|
||||
options.format_control.disallow_binaries()
|
||||
|
|
|
@ -66,7 +66,6 @@ def make_install_req_from_link(
|
|||
isolated=template.isolated,
|
||||
constraint=template.constraint,
|
||||
options=dict(
|
||||
install_options=template.install_options,
|
||||
global_options=template.global_options,
|
||||
hashes=template.hash_options,
|
||||
),
|
||||
|
@ -90,7 +89,6 @@ def make_install_req_from_editable(
|
|||
constraint=template.constraint,
|
||||
permit_editable_wheels=template.permit_editable_wheels,
|
||||
options=dict(
|
||||
install_options=template.install_options,
|
||||
global_options=template.global_options,
|
||||
hashes=template.hash_options,
|
||||
),
|
||||
|
@ -115,7 +113,6 @@ def _make_install_req_from_dist(
|
|||
isolated=template.isolated,
|
||||
constraint=template.constraint,
|
||||
options=dict(
|
||||
install_options=template.install_options,
|
||||
global_options=template.global_options,
|
||||
hashes=template.hash_options,
|
||||
),
|
||||
|
|
|
@ -104,7 +104,7 @@ class PipProvider(_ProviderBase):
|
|||
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
|
||||
return requirement_or_candidate.name
|
||||
|
||||
def get_preference( # type: ignore
|
||||
def get_preference(
|
||||
self,
|
||||
identifier: str,
|
||||
resolutions: Mapping[str, Candidate],
|
||||
|
@ -124,14 +124,29 @@ class PipProvider(_ProviderBase):
|
|||
* If equal, prefer if any requirement is "pinned", i.e. contains
|
||||
operator ``===`` or ``==``.
|
||||
* If equal, calculate an approximate "depth" and resolve requirements
|
||||
closer to the user-specified requirements first.
|
||||
closer to the user-specified requirements first. If the depth cannot
|
||||
by determined (eg: due to no matching parents), it is considered
|
||||
infinite.
|
||||
* Order user-specified requirements by the order they are specified.
|
||||
* If equal, prefers "non-free" requirements, i.e. contains at least one
|
||||
operator, such as ``>=`` or ``<``.
|
||||
* If equal, order alphabetically for consistency (helps debuggability).
|
||||
"""
|
||||
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
|
||||
candidate, ireqs = zip(*lookups)
|
||||
try:
|
||||
next(iter(information[identifier]))
|
||||
except StopIteration:
|
||||
# There is no information for this identifier, so there's no known
|
||||
# candidates.
|
||||
has_information = False
|
||||
else:
|
||||
has_information = True
|
||||
|
||||
if has_information:
|
||||
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
|
||||
candidate, ireqs = zip(*lookups)
|
||||
else:
|
||||
candidate, ireqs = None, ()
|
||||
|
||||
operators = [
|
||||
specifier.operator
|
||||
for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
|
||||
|
@ -146,11 +161,14 @@ class PipProvider(_ProviderBase):
|
|||
requested_order: Union[int, float] = self._user_requested[identifier]
|
||||
except KeyError:
|
||||
requested_order = math.inf
|
||||
parent_depths = (
|
||||
self._known_depths[parent.name] if parent is not None else 0.0
|
||||
for _, parent in information[identifier]
|
||||
)
|
||||
inferred_depth = min(d for d in parent_depths) + 1.0
|
||||
if has_information:
|
||||
parent_depths = (
|
||||
self._known_depths[parent.name] if parent is not None else 0.0
|
||||
for _, parent in information[identifier]
|
||||
)
|
||||
inferred_depth = min(d for d in parent_depths) + 1.0
|
||||
else:
|
||||
inferred_depth = math.inf
|
||||
else:
|
||||
inferred_depth = 1.0
|
||||
self._known_depths[identifier] = inferred_depth
|
||||
|
@ -161,16 +179,6 @@ class PipProvider(_ProviderBase):
|
|||
# free, so we always do it first to avoid needless work if it fails.
|
||||
requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
|
||||
|
||||
# HACK: Setuptools have a very long and solid backward compatibility
|
||||
# track record, and extremely few projects would request a narrow,
|
||||
# non-recent version range of it since that would break a lot things.
|
||||
# (Most projects specify it only to request for an installer feature,
|
||||
# which does not work, but that's another topic.) Intentionally
|
||||
# delaying Setuptools helps reduce branches the resolver has to check.
|
||||
# This serves as a temporary fix for issues like "apache-airflow[all]"
|
||||
# while we work on "proper" branch pruning techniques.
|
||||
delay_this = identifier == "setuptools"
|
||||
|
||||
# Prefer the causes of backtracking on the assumption that the problem
|
||||
# resolving the dependency tree is related to the failures that caused
|
||||
# the backtracking
|
||||
|
@ -178,7 +186,6 @@ class PipProvider(_ProviderBase):
|
|||
|
||||
return (
|
||||
not requires_python,
|
||||
delay_this,
|
||||
not direct,
|
||||
not pinned,
|
||||
not backtrack_cause,
|
||||
|
|
|
@ -11,9 +11,9 @@ logger = getLogger(__name__)
|
|||
|
||||
class PipReporter(BaseReporter):
|
||||
def __init__(self) -> None:
|
||||
self.backtracks_by_package: DefaultDict[str, int] = defaultdict(int)
|
||||
self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)
|
||||
|
||||
self._messages_at_backtrack = {
|
||||
self._messages_at_reject_count = {
|
||||
1: (
|
||||
"pip is looking at multiple versions of {package_name} to "
|
||||
"determine which version is compatible with other "
|
||||
|
@ -32,14 +32,14 @@ class PipReporter(BaseReporter):
|
|||
),
|
||||
}
|
||||
|
||||
def backtracking(self, candidate: Candidate) -> None:
|
||||
self.backtracks_by_package[candidate.name] += 1
|
||||
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
|
||||
self.reject_count_by_package[candidate.name] += 1
|
||||
|
||||
count = self.backtracks_by_package[candidate.name]
|
||||
if count not in self._messages_at_backtrack:
|
||||
count = self.reject_count_by_package[candidate.name]
|
||||
if count not in self._messages_at_reject_count:
|
||||
return
|
||||
|
||||
message = self._messages_at_backtrack[count]
|
||||
message = self._messages_at_reject_count[count]
|
||||
logger.info("INFO: %s", message.format(package_name=candidate.name))
|
||||
|
||||
|
||||
|
@ -61,8 +61,8 @@ class PipDebuggingReporter(BaseReporter):
|
|||
def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None:
|
||||
logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
|
||||
|
||||
def backtracking(self, candidate: Candidate) -> None:
|
||||
logger.info("Reporter.backtracking(%r)", candidate)
|
||||
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
|
||||
logger.info("Reporter.rejecting_candidate(%r, %r)", criterion, candidate)
|
||||
|
||||
def pinning(self, candidate: Candidate) -> None:
|
||||
logger.info("Reporter.pinning(%r)", candidate)
|
||||
|
|
|
@ -133,7 +133,7 @@ class UpgradePrompt:
|
|||
return Group(
|
||||
Text(),
|
||||
Text.from_markup(
|
||||
f"{notice} A new release of pip available: "
|
||||
f"{notice} A new release of pip is available: "
|
||||
f"[red]{self.old}[reset] -> [green]{self.new}[reset]"
|
||||
),
|
||||
Text.from_markup(
|
||||
|
@ -155,7 +155,7 @@ def was_installed_by_pip(pkg: str) -> bool:
|
|||
|
||||
def _get_current_remote_pip_version(
|
||||
session: PipSession, options: optparse.Values
|
||||
) -> str:
|
||||
) -> Optional[str]:
|
||||
# Lets use PackageFinder to see what the latest pip version is
|
||||
link_collector = LinkCollector.create(
|
||||
session,
|
||||
|
@ -176,7 +176,7 @@ def _get_current_remote_pip_version(
|
|||
)
|
||||
best_candidate = finder.find_best_candidate("pip").best_candidate
|
||||
if best_candidate is None:
|
||||
return
|
||||
return None
|
||||
|
||||
return str(best_candidate.version)
|
||||
|
||||
|
@ -186,11 +186,14 @@ def _self_version_check_logic(
|
|||
state: SelfCheckState,
|
||||
current_time: datetime.datetime,
|
||||
local_version: DistributionVersion,
|
||||
get_remote_version: Callable[[], str],
|
||||
get_remote_version: Callable[[], Optional[str]],
|
||||
) -> Optional[UpgradePrompt]:
|
||||
remote_version_str = state.get(current_time)
|
||||
if remote_version_str is None:
|
||||
remote_version_str = get_remote_version()
|
||||
if remote_version_str is None:
|
||||
logger.debug("No remote pip version found")
|
||||
return None
|
||||
state.set(remote_version_str, current_time)
|
||||
|
||||
remote_version = parse_version(remote_version_str)
|
||||
|
|
109
src/pip/_internal/utils/_jaraco_text.py
Normal file
109
src/pip/_internal/utils/_jaraco_text.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
"""Functions brought over from jaraco.text.
|
||||
|
||||
These functions are not supposed to be used within `pip._internal`. These are
|
||||
helper functions brought over from `jaraco.text` to enable vendoring newer
|
||||
copies of `pkg_resources` without having to vendor `jaraco.text` and its entire
|
||||
dependency cone; something that our vendoring setup is not currently capable of
|
||||
handling.
|
||||
|
||||
License reproduced from original source below:
|
||||
|
||||
Copyright Jason R. Coombs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
|
||||
|
||||
def _nonblank(str):
|
||||
return str and not str.startswith("#")
|
||||
|
||||
|
||||
@functools.singledispatch
|
||||
def yield_lines(iterable):
|
||||
r"""
|
||||
Yield valid lines of a string or iterable.
|
||||
|
||||
>>> list(yield_lines(''))
|
||||
[]
|
||||
>>> list(yield_lines(['foo', 'bar']))
|
||||
['foo', 'bar']
|
||||
>>> list(yield_lines('foo\nbar'))
|
||||
['foo', 'bar']
|
||||
>>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
|
||||
['foo', 'baz #comment']
|
||||
>>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
|
||||
['foo', 'bar', 'baz', 'bing']
|
||||
"""
|
||||
return itertools.chain.from_iterable(map(yield_lines, iterable))
|
||||
|
||||
|
||||
@yield_lines.register(str)
|
||||
def _(text):
|
||||
return filter(_nonblank, map(str.strip, text.splitlines()))
|
||||
|
||||
|
||||
def drop_comment(line):
|
||||
"""
|
||||
Drop comments.
|
||||
|
||||
>>> drop_comment('foo # bar')
|
||||
'foo'
|
||||
|
||||
A hash without a space may be in a URL.
|
||||
|
||||
>>> drop_comment('http://example.com/foo#bar')
|
||||
'http://example.com/foo#bar'
|
||||
"""
|
||||
return line.partition(" #")[0]
|
||||
|
||||
|
||||
def join_continuation(lines):
|
||||
r"""
|
||||
Join lines continued by a trailing backslash.
|
||||
|
||||
>>> list(join_continuation(['foo \\', 'bar', 'baz']))
|
||||
['foobar', 'baz']
|
||||
>>> list(join_continuation(['foo \\', 'bar', 'baz']))
|
||||
['foobar', 'baz']
|
||||
>>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
|
||||
['foobarbaz']
|
||||
|
||||
Not sure why, but...
|
||||
The character preceeding the backslash is also elided.
|
||||
|
||||
>>> list(join_continuation(['goo\\', 'dly']))
|
||||
['godly']
|
||||
|
||||
A terrible idea, but...
|
||||
If no line is available to continue, suppress the lines.
|
||||
|
||||
>>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
|
||||
['foo']
|
||||
"""
|
||||
lines = iter(lines)
|
||||
for item in lines:
|
||||
while item.endswith("\\"):
|
||||
try:
|
||||
item = item[:-2].strip() + next(lines)
|
||||
except StopIteration:
|
||||
return
|
||||
yield item
|
|
@ -173,16 +173,3 @@ LegacyInstallReasonMissingWheelPackage = LegacyInstallReason(
|
|||
issue=8559,
|
||||
emit_before_install=True,
|
||||
)
|
||||
|
||||
LegacyInstallReasonNoBinaryForcesSetuptoolsInstall = LegacyInstallReason(
|
||||
reason=(
|
||||
"{name} is being installed using the legacy "
|
||||
"'setup.py install' method, because the '--no-binary' option was enabled "
|
||||
"for it and this currently disables local wheel building for projects that "
|
||||
"don't have a 'pyproject.toml' file."
|
||||
),
|
||||
replacement="to enable the '--use-pep517' option",
|
||||
gone_in="23.1",
|
||||
issue=11451,
|
||||
emit_before_install=True,
|
||||
)
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
from getopt import GetoptError, getopt
|
||||
from typing import Dict, List
|
||||
|
||||
_options = [
|
||||
"exec-prefix=",
|
||||
"home=",
|
||||
"install-base=",
|
||||
"install-data=",
|
||||
"install-headers=",
|
||||
"install-lib=",
|
||||
"install-platlib=",
|
||||
"install-purelib=",
|
||||
"install-scripts=",
|
||||
"prefix=",
|
||||
"root=",
|
||||
"user",
|
||||
]
|
||||
|
||||
|
||||
def parse_distutils_args(args: List[str]) -> Dict[str, str]:
|
||||
"""Parse provided arguments, returning an object that has the matched arguments.
|
||||
|
||||
Any unknown arguments are ignored.
|
||||
"""
|
||||
result = {}
|
||||
for arg in args:
|
||||
try:
|
||||
parsed_opt, _ = getopt(args=[arg], shortopts="", longopts=_options)
|
||||
except GetoptError:
|
||||
# We don't care about any other options, which here may be
|
||||
# considered unrecognized since our option list is not
|
||||
# exhaustive.
|
||||
continue
|
||||
|
||||
if not parsed_opt:
|
||||
continue
|
||||
|
||||
option = parsed_opt[0]
|
||||
name_from_parsed = option[0][2:].replace("-", "_")
|
||||
value_from_parsed = option[1] or "true"
|
||||
result[name_from_parsed] = value_from_parsed
|
||||
|
||||
return result
|
|
@ -1,10 +1,7 @@
|
|||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from pip._internal.locations import site_packages, user_site
|
||||
from pip._internal.utils.virtualenv import (
|
||||
|
@ -57,7 +54,7 @@ def egg_link_path_from_location(raw_name: str) -> Optional[str]:
|
|||
|
||||
This method will just return the first one found.
|
||||
"""
|
||||
sites = []
|
||||
sites: List[str] = []
|
||||
if running_under_virtualenv():
|
||||
sites.append(site_packages)
|
||||
if not virtualenv_no_global() and user_site:
|
||||
|
|
|
@ -12,6 +12,7 @@ import posixpath
|
|||
import shutil
|
||||
import stat
|
||||
import sys
|
||||
import sysconfig
|
||||
import urllib.parse
|
||||
from io import StringIO
|
||||
from itertools import filterfalse, tee, zip_longest
|
||||
|
@ -34,11 +35,11 @@ from typing import (
|
|||
cast,
|
||||
)
|
||||
|
||||
from pip._vendor.pep517 import Pep517HookCaller
|
||||
from pip._vendor.pyproject_hooks import BuildBackendHookCaller
|
||||
from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.exceptions import CommandError, ExternallyManagedEnvironment
|
||||
from pip._internal.locations import get_major_minor_version
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
@ -57,10 +58,10 @@ __all__ = [
|
|||
"captured_stdout",
|
||||
"ensure_dir",
|
||||
"remove_auth_from_url",
|
||||
"ConfiguredPep517HookCaller",
|
||||
"check_externally_managed",
|
||||
"ConfiguredBuildBackendHookCaller",
|
||||
]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
@ -581,6 +582,21 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
|
|||
)
|
||||
|
||||
|
||||
def check_externally_managed() -> None:
|
||||
"""Check whether the current environment is externally managed.
|
||||
|
||||
If the ``EXTERNALLY-MANAGED`` config file is found, the current environment
|
||||
is considered externally managed, and an ExternallyManagedEnvironment is
|
||||
raised.
|
||||
"""
|
||||
if running_under_virtualenv():
|
||||
return
|
||||
marker = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED")
|
||||
if not os.path.isfile(marker):
|
||||
return
|
||||
raise ExternallyManagedEnvironment.from_config(marker)
|
||||
|
||||
|
||||
def is_console_interactive() -> bool:
|
||||
"""Is this console interactive?"""
|
||||
return sys.stdin is not None and sys.stdin.isatty()
|
||||
|
@ -635,7 +651,7 @@ def partition(
|
|||
return filterfalse(pred, t1), filter(pred, t2)
|
||||
|
||||
|
||||
class ConfiguredPep517HookCaller(Pep517HookCaller):
|
||||
class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
|
||||
def __init__(
|
||||
self,
|
||||
config_holder: Any,
|
||||
|
|
|
@ -103,8 +103,8 @@ def make_setuptools_clean_args(
|
|||
|
||||
def make_setuptools_develop_args(
|
||||
setup_py_path: str,
|
||||
*,
|
||||
global_options: Sequence[str],
|
||||
install_options: Sequence[str],
|
||||
no_user_config: bool,
|
||||
prefix: Optional[str],
|
||||
home: Optional[str],
|
||||
|
@ -120,8 +120,6 @@ def make_setuptools_develop_args(
|
|||
|
||||
args += ["develop", "--no-deps"]
|
||||
|
||||
args += install_options
|
||||
|
||||
if prefix:
|
||||
args += ["--prefix", prefix]
|
||||
if home is not None:
|
||||
|
@ -150,8 +148,8 @@ def make_setuptools_egg_info_args(
|
|||
|
||||
def make_setuptools_install_args(
|
||||
setup_py_path: str,
|
||||
*,
|
||||
global_options: Sequence[str],
|
||||
install_options: Sequence[str],
|
||||
record_filename: str,
|
||||
root: Optional[str],
|
||||
prefix: Optional[str],
|
||||
|
@ -190,6 +188,4 @@ def make_setuptools_install_args(
|
|||
if header_dir:
|
||||
args += ["--install-headers", header_dir]
|
||||
|
||||
args += install_options
|
||||
|
||||
return args
|
||||
|
|
|
@ -239,8 +239,8 @@ def call_subprocess(
|
|||
def runner_with_spinner_message(message: str) -> Callable[..., None]:
|
||||
"""Provide a subprocess_runner that shows a spinner message.
|
||||
|
||||
Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
|
||||
an API that matches what's expected by Pep517HookCaller.subprocess_runner.
|
||||
Intended for use with for BuildBackendHookCaller. Thus, the runner has
|
||||
an API that matches what's expected by BuildBackendHookCaller.subprocess_runner.
|
||||
"""
|
||||
|
||||
def runner(
|
||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
|||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
from typing import Callable, Iterable, List, Optional, Tuple
|
||||
from typing import Iterable, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version
|
||||
from pip._vendor.packaging.version import InvalidVersion, Version
|
||||
|
@ -19,10 +19,7 @@ from pip._internal.operations.build.wheel import build_wheel_pep517
|
|||
from pip._internal.operations.build.wheel_editable import build_wheel_editable
|
||||
from pip._internal.operations.build.wheel_legacy import build_wheel_legacy
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.deprecation import (
|
||||
LegacyInstallReasonMissingWheelPackage,
|
||||
LegacyInstallReasonNoBinaryForcesSetuptoolsInstall,
|
||||
)
|
||||
from pip._internal.utils.deprecation import LegacyInstallReasonMissingWheelPackage
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_clean_args
|
||||
|
@ -35,7 +32,6 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)
|
||||
|
||||
BdistWheelAllowedPredicate = Callable[[InstallRequirement], bool]
|
||||
BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]
|
||||
|
||||
|
||||
|
@ -50,7 +46,6 @@ def _contains_egg_info(s: str) -> bool:
|
|||
def _should_build(
|
||||
req: InstallRequirement,
|
||||
need_wheel: bool,
|
||||
check_bdist_wheel: Optional[BdistWheelAllowedPredicate] = None,
|
||||
) -> bool:
|
||||
"""Return whether an InstallRequirement should be built into a wheel."""
|
||||
if req.constraint:
|
||||
|
@ -81,16 +76,6 @@ def _should_build(
|
|||
if req.use_pep517:
|
||||
return True
|
||||
|
||||
assert check_bdist_wheel is not None
|
||||
if not check_bdist_wheel(req):
|
||||
# /!\ When we change this to unconditionally return True, we must also remove
|
||||
# support for `--install-option`. Indeed, `--install-option` implies
|
||||
# `--no-binary` so we can return False here and run `setup.py install`.
|
||||
# `--global-option` and `--build-option` can remain until we drop support for
|
||||
# building with `setup.py bdist_wheel`.
|
||||
req.legacy_install_reason = LegacyInstallReasonNoBinaryForcesSetuptoolsInstall
|
||||
return False
|
||||
|
||||
if not is_wheel_installed():
|
||||
# we don't build legacy requirements if wheel is not installed
|
||||
req.legacy_install_reason = LegacyInstallReasonMissingWheelPackage
|
||||
|
@ -107,11 +92,8 @@ def should_build_for_wheel_command(
|
|||
|
||||
def should_build_for_install_command(
|
||||
req: InstallRequirement,
|
||||
check_bdist_wheel_allowed: BdistWheelAllowedPredicate,
|
||||
) -> bool:
|
||||
return _should_build(
|
||||
req, need_wheel=False, check_bdist_wheel=check_bdist_wheel_allowed
|
||||
)
|
||||
return _should_build(req, need_wheel=False)
|
||||
|
||||
|
||||
def _should_cache(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .core import contents, where
|
||||
|
||||
__all__ = ["contents", "where"]
|
||||
__version__ = "2022.09.24"
|
||||
__version__ = "2022.12.07"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue