mirror of https://github.com/pypa/pip
Merge branch 'master' into master
This commit is contained in:
commit
e64742d745
|
@ -18,7 +18,8 @@ environment:
|
|||
install:
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
- "python --version"
|
||||
- "pip install certifi tox"
|
||||
- "pip install --upgrade certifi tox tox-venv"
|
||||
- "pip freeze --all"
|
||||
# Fix git SSL errors.
|
||||
- "python -m certifi >cacert.txt"
|
||||
- "set /p GIT_SSL_CAINFO=<cacert.txt"
|
||||
|
@ -61,7 +62,7 @@ test_script:
|
|||
$env:TEMP = "T:\"
|
||||
$env:TMP = "T:\"
|
||||
if ($env:RUN_INTEGRATION_TESTS -eq "True") {
|
||||
tox -e py -- -m integration -n 3 --duration=5
|
||||
tox -e py -- --use-venv -m integration -n 3 --duration=5
|
||||
}
|
||||
else {
|
||||
tox -e py -- -m unit -n 3
|
|
@ -26,6 +26,7 @@ htmlcov/
|
|||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
tests/data/common_wheels/
|
||||
|
||||
# Misc
|
||||
*~
|
||||
|
|
2
.mailmap
2
.mailmap
|
@ -1,4 +1,6 @@
|
|||
Adam Wentz <awentz@theonion.com>
|
||||
Alethea Flowers <magicalgirl@google.com> <jjramone13@gmail.com>
|
||||
Alethea Flowers <magicalgirl@google.com> Thea Flowers <theaflowers@google.com>
|
||||
Alex Grönholm <alex.gronholm@nextday.fi>
|
||||
Alex Grönholm <alex.gronholm@nextday.fi> <alex.gronholm+git@nextday.fi>
|
||||
Anatoly Techtonik <techtonik@gmail.com>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
language: python
|
||||
sudo: false
|
||||
cache: pip
|
||||
dist: trusty
|
||||
python: 3.6
|
||||
|
||||
stages:
|
||||
- primary
|
||||
|
@ -13,8 +13,8 @@ jobs:
|
|||
- stage: primary
|
||||
env: TOXENV=docs
|
||||
- env: TOXENV=lint-py2
|
||||
python: 2.7
|
||||
- env: TOXENV=lint-py3
|
||||
python: 3.6
|
||||
- env: TOXENV=mypy
|
||||
- env: TOXENV=packaging
|
||||
# Latest CPython
|
||||
|
@ -41,11 +41,9 @@ jobs:
|
|||
- env: GROUP=1
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: required
|
||||
- env: GROUP=2
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: required
|
||||
- env: GROUP=1
|
||||
python: 3.5
|
||||
- env: GROUP=2
|
||||
|
@ -58,11 +56,9 @@ jobs:
|
|||
- env: GROUP=1
|
||||
python: 3.8-dev
|
||||
dist: xenial
|
||||
sudo: required
|
||||
- env: GROUP=2
|
||||
python: 3.8-dev
|
||||
dist: xenial
|
||||
sudo: required
|
||||
|
||||
# It's okay to fail on the in-development CPython version.
|
||||
fast_finish: true
|
||||
|
|
30
AUTHORS.txt
30
AUTHORS.txt
|
@ -1,17 +1,22 @@
|
|||
AceGentile <ventogrigio83@gmail.com>
|
||||
Adam Chainz <adam@adamj.eu>
|
||||
Adam Wentz <awentz@theonion.com>
|
||||
Adrien Morison <adrien.morison@gmail.com>
|
||||
Alan Yee <alyee@ucsd.edu>
|
||||
Aleks Bunin <github@compuix.com>
|
||||
Alethea Flowers <magicalgirl@google.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alex Grönholm <alex.gronholm@nextday.fi>
|
||||
Alex Morega <alex@grep.ro>
|
||||
Alex Stachowiak <alexander@computer.org>
|
||||
Alexander Shtyrov <rawzausho@gmail.com>
|
||||
Alexandre Conrad <alexandre.conrad@gmail.com>
|
||||
Alexey Popravka <a.popravka@smartweb.com.ua>
|
||||
Alexey Popravka <alexey.popravka@horsedevel.com>
|
||||
Alli <alzeih@users.noreply.github.com>
|
||||
Anatoly Techtonik <techtonik@gmail.com>
|
||||
Andrei Geacar <andrei.geacar@gmail.com>
|
||||
Andrew Gaul <andrew@gaul.org>
|
||||
Andrey Bulgakov <mail@andreiko.ru>
|
||||
Andrés Delfino <34587441+andresdelfino@users.noreply.github.com>
|
||||
Andrés Delfino <adelfino@gmail.com>
|
||||
|
@ -36,6 +41,8 @@ Ashley Manton <ajd.manton@googlemail.com>
|
|||
Atsushi Odagiri <aodagx@gmail.com>
|
||||
Avner Cohen <israbirding@gmail.com>
|
||||
Baptiste Mispelon <bmispelon@gmail.com>
|
||||
Barney Gale <barney.gale@gmail.com>
|
||||
barneygale <barney.gale@gmail.com>
|
||||
Bartek Ogryczak <b.ogryczak@gmail.com>
|
||||
Bastian Venthur <mail@venthur.de>
|
||||
Ben Darnell <ben@bendarnell.com>
|
||||
|
@ -46,6 +53,7 @@ Benjamin VanEvery <ben@simondata.com>
|
|||
Benoit Pierre <benoit.pierre@gmail.com>
|
||||
Berker Peksag <berker.peksag@gmail.com>
|
||||
Bernardo B. Marques <bernardo.fire@gmail.com>
|
||||
Bernhard M. Wiedemann <bwiedemann@suse.de>
|
||||
Bogdan Opanchuk <bogdan@opanchuk.net>
|
||||
Brad Erickson <eosrei@gmail.com>
|
||||
Bradley Ayers <bradley.ayers@gmail.com>
|
||||
|
@ -55,6 +63,7 @@ Brian Rosner <brosner@gmail.com>
|
|||
BrownTruck <BrownTruck@users.noreply.github.com>
|
||||
Bruno Oliveira <nicoddemus@gmail.com>
|
||||
Bruno Renié <brutasse@gmail.com>
|
||||
Bstrdsmkr <bstrdsmkr@gmail.com>
|
||||
Buck Golemon <buck@yelp.com>
|
||||
burrows <burrows@preveil.com>
|
||||
Bussonnier Matthias <bussonniermatthias@gmail.com>
|
||||
|
@ -86,6 +95,7 @@ Cory Wright <corywright@gmail.com>
|
|||
Craig Kerstiens <craig.kerstiens@gmail.com>
|
||||
Cristian Sorinel <cristian.sorinel@gmail.com>
|
||||
Curtis Doty <Curtis@GreenKey.net>
|
||||
cytolentino <ctolentino8@bloomberg.net>
|
||||
Damian Quiroga <qdamian@gmail.com>
|
||||
Dan Black <dyspop@gmail.com>
|
||||
Dan Savilonis <djs@n-cube.org>
|
||||
|
@ -100,6 +110,7 @@ Daniele Procida <daniele@vurt.org>
|
|||
Danny Hermes <daniel.j.hermes@gmail.com>
|
||||
Dav Clark <davclark@gmail.com>
|
||||
Dave Abrahams <dave@boostpro.com>
|
||||
Dave Jones <dave@waveform.org.uk>
|
||||
David Aguilar <davvid@gmail.com>
|
||||
David Black <db@d1b.org>
|
||||
David Caro <david@dcaro.es>
|
||||
|
@ -120,6 +131,7 @@ Dustin Ingram <di@di.codes>
|
|||
Dwayne Bailey <dwayne@translate.org.za>
|
||||
Ed Morley <501702+edmorley@users.noreply.github.com>
|
||||
Ed Morley <emorley@mozilla.com>
|
||||
elainechan <elaine.chan@outlook.com>
|
||||
Eli Schwartz <eschwartz93@gmail.com>
|
||||
Emil Styrke <emil.styrke@gmail.com>
|
||||
Endoh Takanao <djmchl@gmail.com>
|
||||
|
@ -133,6 +145,7 @@ Ernest W Durbin III <ewdurbin@gmail.com>
|
|||
Ernest W. Durbin III <ewdurbin@gmail.com>
|
||||
Erwin Janssen <erwinjanssen@outlook.com>
|
||||
Eugene Vereshchagin <evvers@gmail.com>
|
||||
Felix Yan <felixonmars@archlinux.org>
|
||||
fiber-space <fiber-space@users.noreply.github.com>
|
||||
Filip Kokosiński <filip.kokosinski@gmail.com>
|
||||
Florian Briand <ownerfrance+github@hotmail.com>
|
||||
|
@ -157,6 +170,7 @@ Herbert Pfennig <herbert@albinen.com>
|
|||
Hsiaoming Yang <lepture@me.com>
|
||||
Hugo <hugovk@users.noreply.github.com>
|
||||
Hugo Lopes Tavares <hltbra@gmail.com>
|
||||
hugovk <hugovk@users.noreply.github.com>
|
||||
Hynek Schlawack <hs@ox.cx>
|
||||
Ian Bicking <ianb@colorstudy.com>
|
||||
Ian Cordasco <graffatcolmingov@gmail.com>
|
||||
|
@ -182,6 +196,7 @@ Jannis Leidel <jannis@leidel.info>
|
|||
jarondl <me@jarondl.net>
|
||||
Jason R. Coombs <jaraco@jaraco.com>
|
||||
Jay Graves <jay@skabber.com>
|
||||
Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>
|
||||
Jeff Barber <jbarber@computer.org>
|
||||
Jeff Dairiki <dairiki@dairiki.org>
|
||||
Jeremy Stanley <fungi@yuggoth.org>
|
||||
|
@ -192,8 +207,8 @@ John-Scott Atlakson <john.scott.atlakson@gmail.com>
|
|||
Jon Banafato <jon@jonafato.com>
|
||||
Jon Dufresne <jon.dufresne@gmail.com>
|
||||
Jon Parise <jon@indelible.org>
|
||||
Jon Wayne Parrott <jjramone13@gmail.com>
|
||||
Jonas Nockert <jonasnockert@gmail.com>
|
||||
Jonathan Herbert <foohyfooh@gmail.com>
|
||||
Joost Molenaar <j.j.molenaar@gmail.com>
|
||||
Jorge Niedbalski <niedbalski@gmail.com>
|
||||
Joseph Long <jdl@fastmail.fm>
|
||||
|
@ -213,16 +228,20 @@ Kenneth Reitz <me@kennethreitz.org>
|
|||
Kevin Burke <kev@inburke.com>
|
||||
Kevin Carter <kevin.carter@rackspace.com>
|
||||
Kevin Frommelt <kevin.frommelt@webfilings.com>
|
||||
Kevin R Patterson <kevin.r.patterson@intel.com>
|
||||
Kexuan Sun <me@kianasun.com>
|
||||
Kit Randel <kit@nocturne.net.nz>
|
||||
kpinc <kop@meme.com>
|
||||
Kumar McMillan <kumar.mcmillan@gmail.com>
|
||||
Kyle Persohn <kyle.persohn@gmail.com>
|
||||
lakshmanaram <lakshmanaram.n@gmail.com>
|
||||
Laurent Bristiel <laurent@bristiel.com>
|
||||
Laurie Opperman <laurie@sitesee.com.au>
|
||||
Leon Sasson <leonsassonha@gmail.com>
|
||||
Lev Givon <lev@columbia.edu>
|
||||
Lincoln de Sousa <lincoln@comum.org>
|
||||
Lipis <lipiridis@gmail.com>
|
||||
Loren Carvalho <lcarvalho@linkedin.com>
|
||||
Lucas Cimon <lucas.cimon@gmail.com>
|
||||
Ludovic Gasc <gmludo@gmail.com>
|
||||
Luke Macken <lmacken@redhat.com>
|
||||
|
@ -259,6 +278,7 @@ Michael E. Karpeles <michael.karpeles@gmail.com>
|
|||
Michael Klich <michal@michalklich.com>
|
||||
Michael Williamson <mike@zwobble.org>
|
||||
michaelpacer <michaelpacer@gmail.com>
|
||||
Mickaël Schoentgen <mschoentgen@nuxeo.com>
|
||||
Miguel Araujo Perez <miguel.araujo.perez@gmail.com>
|
||||
Mihir Singh <git.service@mihirsingh.com>
|
||||
Min RK <benjaminrk@gmail.com>
|
||||
|
@ -272,6 +292,7 @@ Nehal J Wani <nehaljw.kkd1@gmail.com>
|
|||
Nick Coghlan <ncoghlan@gmail.com>
|
||||
Nick Stenning <nick@whiteink.com>
|
||||
Nikhil Benesch <nikhil.benesch@gmail.com>
|
||||
Nitesh Sharma <nbsharma@outlook.com>
|
||||
Nowell Strite <nowell@strite.org>
|
||||
nvdv <modestdev@gmail.com>
|
||||
Ofekmeister <ofekmeister@gmail.com>
|
||||
|
@ -294,9 +315,11 @@ Paul Nasrat <pnasrat@gmail.com>
|
|||
Paul Oswald <pauloswald@gmail.com>
|
||||
Paul van der Linden <mail@paultjuh.org>
|
||||
Paulus Schoutsen <paulus@paulusschoutsen.nl>
|
||||
Pavithra Eswaramoorthy <33131404+QueenCoffee@users.noreply.github.com>
|
||||
Pawel Jasinski <pawel.jasinski@gmail.com>
|
||||
Pekka Klärck <peke@iki.fi>
|
||||
Peter Waller <peter.waller@gmail.com>
|
||||
petr-tik <petr-tik@users.noreply.github.com>
|
||||
Phaneendra Chiruvella <hi@pcx.io>
|
||||
Phil Freo <phil@philfreo.com>
|
||||
Phil Pennock <phil@pennock-tech.com>
|
||||
|
@ -339,6 +362,7 @@ Sachi King <nakato@nakato.io>
|
|||
Salvatore Rinchiera <salvatore@rinchiera.com>
|
||||
schlamar <marc.schlaich@gmail.com>
|
||||
Scott Kitterman <sklist@kitterman.com>
|
||||
Sean <me@sean.taipei>
|
||||
seanj <seanj@xyke.com>
|
||||
Sebastian Schaetz <sschaetz@butterflynetinc.com>
|
||||
Segev Finer <segev208@gmail.com>
|
||||
|
@ -374,12 +398,14 @@ Tim Harder <radhermit@gmail.com>
|
|||
Tim Heap <tim@timheap.me>
|
||||
tim smith <github@tim-smith.us>
|
||||
tinruufu <tinruufu@gmail.com>
|
||||
Tom Forbes <tom@tomforb.es>
|
||||
Tom Freudenheim <tom.freudenheim@onepeloton.com>
|
||||
Tom V <tom@viner.tv>
|
||||
Tomer Chachamu <tomer.chachamu@gmail.com>
|
||||
Tony Zhaocheng Tan <tony@tonytan.io>
|
||||
Toshio Kuratomi <toshio@fedoraproject.org>
|
||||
Travis Swicegood <development@domain51.com>
|
||||
Tzu-ping Chung <uranusjr@gmail.com>
|
||||
Valentin Haenel <valentin.haenel@gmx.de>
|
||||
Victor Stinner <victor.stinner@gmail.com>
|
||||
Viktor Szépe <viktor@szepe.net>
|
||||
|
@ -392,6 +418,7 @@ W. Trevor King <wking@drexel.edu>
|
|||
Wil Tan <wil@dready.org>
|
||||
Wilfred Hughes <me@wilfred.me.uk>
|
||||
William ML Leslie <william.leslie.ttg@gmail.com>
|
||||
wim glenn <wim.glenn@gmail.com>
|
||||
Wolfgang Maier <wolfgang.maier@biologie.uni-freiburg.de>
|
||||
Xavier Fernandez <xav.fernandez@gmail.com>
|
||||
Xavier Fernandez <xavier.fernandez@polyconseil.fr>
|
||||
|
@ -400,6 +427,7 @@ YAMAMOTO Takashi <yamamoto@midokura.com>
|
|||
Yen Chi Hsuan <yan12125@gmail.com>
|
||||
Yoval P <yoval@gmx.com>
|
||||
Yu Jian <askingyj@gmail.com>
|
||||
Yuan Jing Vincent Yan <yyan82@bloomberg.net>
|
||||
Zearin <zearin@gonk.net>
|
||||
Zearin <Zearin@users.noreply.github.com>
|
||||
Zhiping Deng <kofreestyler@gmail.com>
|
||||
|
|
|
@ -6,14 +6,16 @@ include pyproject.toml
|
|||
|
||||
include src/pip/_vendor/README.rst
|
||||
include src/pip/_vendor/vendor.txt
|
||||
recursive-include src/pip/_vendor *LICENSE*
|
||||
recursive-include src/pip/_vendor *COPYING*
|
||||
|
||||
include docs/docutils.conf
|
||||
|
||||
exclude .coveragerc
|
||||
exclude .mailmap
|
||||
exclude .appveyor.yml
|
||||
exclude .travis.yml
|
||||
exclude tox.ini
|
||||
exclude appveyor.yml
|
||||
|
||||
recursive-include src/pip/_vendor *.pem
|
||||
recursive-include docs Makefile *.rst *.py *.bat
|
||||
|
|
51
NEWS.rst
51
NEWS.rst
|
@ -7,6 +7,53 @@
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
18.1 (2018-10-05)
|
||||
=================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Allow PEP 508 URL requirements to be used as dependencies.
|
||||
|
||||
As a security measure, pip will raise an exception when installing packages from
|
||||
PyPI if those packages depend on packages not also hosted on PyPI.
|
||||
In the future, PyPI will block uploading packages with such external URL dependencies directly. (`#4187 <https://github.com/pypa/pip/issues/4187>`_)
|
||||
- Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target (`#5355 <https://github.com/pypa/pip/issues/5355>`_)
|
||||
- Support passing ``svn+ssh`` URLs with a username to ``pip install -e``. (`#5375 <https://github.com/pypa/pip/issues/5375>`_)
|
||||
- pip now ensures that the RECORD file is sorted when installing from a wheel file. (`#5525 <https://github.com/pypa/pip/issues/5525>`_)
|
||||
- Add support for Python 3.7. (`#5561 <https://github.com/pypa/pip/issues/5561>`_)
|
||||
- Malformed configuration files now show helpful error messages, instead of tracebacks. (`#5798 <https://github.com/pypa/pip/issues/5798>`_)
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Checkout the correct branch when doing an editable Git install. (`#2037 <https://github.com/pypa/pip/issues/2037>`_)
|
||||
- Run self-version-check only on commands that may access the index, instead of
|
||||
trying on every run and failing to do so due to missing options. (`#5433 <https://github.com/pypa/pip/issues/5433>`_)
|
||||
- Allow a Git ref to be installed over an existing installation. (`#5624 <https://github.com/pypa/pip/issues/5624>`_)
|
||||
- Show a better error message when a configuration option has an invalid value. (`#5644 <https://github.com/pypa/pip/issues/5644>`_)
|
||||
- Always revalidate cached simple API pages instead of blindly caching them for up to 10
|
||||
minutes. (`#5670 <https://github.com/pypa/pip/issues/5670>`_)
|
||||
- Avoid caching self-version-check information when cache is disabled. (`#5679 <https://github.com/pypa/pip/issues/5679>`_)
|
||||
- Avoid traceback printing on autocomplete after flags in the CLI. (`#5751 <https://github.com/pypa/pip/issues/5751>`_)
|
||||
- Fix incorrect parsing of egg names if pip needs to guess the package name. (`#5819 <https://github.com/pypa/pip/issues/5819>`_)
|
||||
|
||||
Vendored Libraries
|
||||
------------------
|
||||
|
||||
- Upgrade certifi to 2018.8.24
|
||||
- Upgrade packaging to 18.0
|
||||
- Upgrade pyparsing to 2.2.1
|
||||
- Add pep517 version 0.2
|
||||
- Upgrade pytoml to 0.1.19
|
||||
- Upgrade pkg_resources to 40.4.3 (via setuptools)
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Fix "Requirements Files" reference in User Guide (`#user_guide_fix_requirements_file_ref <https://github.com/pypa/pip/issues/user_guide_fix_requirements_file_ref>`_)
|
||||
|
||||
|
||||
18.0 (2018-07-22)
|
||||
=================
|
||||
|
||||
|
@ -17,7 +64,7 @@ Process
|
|||
- Formally document our deprecation process as a minimum of 6 months of deprecation
|
||||
warnings.
|
||||
- Adopt and document NEWS fragment writing style.
|
||||
- Switch to releasing a new, non bug fix version of pip every 3 months.
|
||||
- Switch to releasing a new, non-bug fix version of pip every 3 months.
|
||||
|
||||
Deprecations and Removals
|
||||
-------------------------
|
||||
|
@ -118,7 +165,7 @@ Bug Fixes
|
|||
---------
|
||||
|
||||
- Prevent false-positive installation warnings due to incomplete name
|
||||
normalizaton. (#5134)
|
||||
normalization. (#5134)
|
||||
- Fix issue where installing from Git with a short SHA would fail. (#5140)
|
||||
- Accept pre-release versions when checking for conflicts with pip check or pip
|
||||
install. (#5141)
|
||||
|
|
|
@ -22,8 +22,8 @@ The `PyPA recommended`_ tool for installing Python packages.
|
|||
* `Issue Tracking`_
|
||||
* `User mailing list`_
|
||||
* `Dev mailing list`_
|
||||
* User IRC: #pypa on Freenode.
|
||||
* Dev IRC: #pypa-dev on Freenode.
|
||||
* `User IRC`_
|
||||
* `Dev IRC`_
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
@ -39,4 +39,6 @@ rooms and mailing lists is expected to follow the `PyPA Code of Conduct`_.
|
|||
.. _Issue Tracking: https://github.com/pypa/pip/issues
|
||||
.. _User mailing list: https://groups.google.com/forum/#!forum/python-virtualenv
|
||||
.. _Dev mailing list: https://groups.google.com/forum/#!forum/pypa-dev
|
||||
.. _User IRC: https://webchat.freenode.net/?channels=%23pypa
|
||||
.. _Dev IRC: https://webchat.freenode.net/?channels=%23pypa-dev
|
||||
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
|
||||
|
|
|
@ -38,7 +38,7 @@ Automated Testing
|
|||
=================
|
||||
|
||||
All pull requests and merges to 'master' branch are tested using `Travis CI`_
|
||||
and `Appveyor CI`_ based on our `.travis.yml`_ and `appveyor.yml`_ files.
|
||||
and `Appveyor CI`_ based on our `.travis.yml`_ and `.appveyor.yml`_ files.
|
||||
|
||||
You can find the status and results to the CI runs for your PR on GitHub's Web
|
||||
UI for the pull request. You can also find links to the CI services' pages for
|
||||
|
@ -245,5 +245,5 @@ and they will initiate a vote.
|
|||
.. _`Travis CI`: https://travis-ci.org/
|
||||
.. _`Appveyor CI`: https://www.appveyor.com/
|
||||
.. _`.travis.yml`: https://github.com/pypa/pip/blob/master/.travis.yml
|
||||
.. _`appveyor.yml`: https://github.com/pypa/pip/blob/master/appveyor.yml
|
||||
.. _`.appveyor.yml`: https://github.com/pypa/pip/blob/master/.appveyor.yml
|
||||
.. _`towncrier`: https://pypi.org/project/towncrier/
|
||||
|
|
|
@ -8,8 +8,8 @@ This document is meant to get you setup to work on pip and to act as a guide and
|
|||
reference to the the development setup. If you face any issues during this
|
||||
process, please `open an issue`_ about it on the issue tracker.
|
||||
|
||||
Development tools
|
||||
=================
|
||||
Development Environment
|
||||
-----------------------
|
||||
|
||||
pip uses :pypi:`tox` for testing against multiple different Python environments
|
||||
and ensuring reproducible environments for linting and building documentation.
|
||||
|
|
|
@ -15,6 +15,7 @@ or the `pypa-dev mailing list`_, to ask questions or get involved.
|
|||
getting-started
|
||||
contributing
|
||||
release-process
|
||||
vendoring-policy
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
.. include:: ../../../src/pip/_vendor/README.rst
|
|
@ -158,6 +158,8 @@ appropriately.
|
|||
does not support the use of environment markers and extras (only version
|
||||
specifiers are respected).
|
||||
|
||||
* ``pip<18.1``: build dependencies using .pth files are not properly supported;
|
||||
as a result namespace packages do not work under Python 3.2 and earlier.
|
||||
|
||||
Future Developments
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -351,11 +351,9 @@ the :ref:`--editable <install_--editable>` option) or not.
|
|||
(referencing a specific commit) if and only if the install is done using the
|
||||
editable option.
|
||||
|
||||
The "project name" component of the url suffix "egg=<project name>-<version>"
|
||||
The "project name" component of the url suffix "egg=<project name>"
|
||||
is used by pip in its dependency logic to identify the project prior
|
||||
to pip downloading and analyzing the metadata. The optional "version"
|
||||
component of the egg name is not functionally important. It merely
|
||||
provides a human-readable clue as to what version is in use. For projects
|
||||
to pip downloading and analyzing the metadata. For projects
|
||||
where setup.py is not in the root of project, "subdirectory" component
|
||||
is used. Value of "subdirectory" component should be a path starting from root
|
||||
of the project to where setup.py is located.
|
||||
|
|
|
@ -493,10 +493,10 @@ to PyPI.
|
|||
|
||||
First, download the archives that fulfill your requirements::
|
||||
|
||||
$ pip install --download DIR -r requirements.txt
|
||||
$ pip download --destination-directory DIR -r requirements.txt
|
||||
|
||||
|
||||
Note that ``pip install --download`` will look in your wheel cache first, before
|
||||
Note that ``pip download`` will look in your wheel cache first, before
|
||||
trying to download from PyPI. If you've never installed your requirements
|
||||
before, you won't have a wheel cache for those items. In that case, if some of
|
||||
your requirements don't come as wheels from PyPI, and you want wheels, then run
|
||||
|
|
|
@ -17,7 +17,9 @@ class PipCommandUsage(rst.Directive):
|
|||
|
||||
def run(self):
|
||||
cmd = commands[self.arguments[0]]
|
||||
usage = dedent(cmd.usage.replace('%prog', 'pip')).strip()
|
||||
usage = dedent(
|
||||
cmd.usage.replace('%prog', 'pip {}'.format(cmd.name))
|
||||
).strip()
|
||||
node = nodes.literal_block(usage, usage)
|
||||
return [node]
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Include the package name in a freeze warning if the package is not installed.
|
|
@ -1 +0,0 @@
|
|||
Checkout the correct branch when doing an editable Git install.
|
|
@ -1,5 +0,0 @@
|
|||
Allow PEP 508 URL requirements to be used as dependencies.
|
||||
|
||||
As a security measure, pip will raise an exception when installing packages from
|
||||
PyPI if those packages depend on packages not also hosted on PyPI.
|
||||
In the future, PyPI will block uploading packages with such external URL dependencies directly.
|
|
@ -0,0 +1 @@
|
|||
Redact the password from the URL in various log messages.
|
|
@ -0,0 +1 @@
|
|||
give 401 warning if username/password do not work for URL
|
|
@ -1,2 +0,0 @@
|
|||
Upgrade pyparsing to 2.2.1.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Editable, non-VCS installs now freeze as editable.
|
|
@ -0,0 +1 @@
|
|||
Invalid requirement no longer causes stack trace to be printed.
|
|
@ -0,0 +1 @@
|
|||
Pip now includes license text of 3rd party libraries.
|
|
@ -0,0 +1,2 @@
|
|||
Handle `requests.exceptions.RetryError` raised in `PackageFinder` that was
|
||||
causing pip to fail silently when some indexes were unreachable.
|
|
@ -1 +0,0 @@
|
|||
Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target
|
|
@ -1 +0,0 @@
|
|||
Support passing ``svn+ssh`` URLs with a username to ``pip install -e``.
|
|
@ -0,0 +1 @@
|
|||
Setting ``PIP_NO_CACHE_DIR=yes`` no longer causes pip to crash.
|
|
@ -1,2 +0,0 @@
|
|||
Run self-version-check only on commands that may access the index, instead of
|
||||
trying on every run and failing to do so due to missing options.
|
|
@ -0,0 +1,2 @@
|
|||
Handle `requests.exceptions.RetryError` raised in `PackageFinder` that was
|
||||
causing pip to fail silently when some indexes were unreachable.
|
|
@ -1 +0,0 @@
|
|||
pip now ensures that the RECORD file is sorted when installing from a wheel file.
|
|
@ -1 +0,0 @@
|
|||
Add support for Python 3.7.
|
|
@ -1 +0,0 @@
|
|||
Allow a Git ref to be installed over an existing installation.
|
|
@ -1 +0,0 @@
|
|||
Show a better error message when a configuration option has an invalid value.
|
|
@ -0,0 +1 @@
|
|||
- Improve PEP 518 build isolation: handle .pth files, so namespace packages are correctly supported under Python 3.2 and earlier.
|
|
@ -1,2 +0,0 @@
|
|||
Always revalidate cached simple API pages instead of blindly caching them for up to 10
|
||||
minutes.
|
|
@ -1 +0,0 @@
|
|||
Avoid caching self-version-check information when cache is disabled.
|
|
@ -0,0 +1,2 @@
|
|||
Make ``PIP_NO_CACHE_DIR`` disable the cache also for truthy values like
|
||||
``"true"``, ``"yes"``, ``"1"``, etc.
|
|
@ -1 +0,0 @@
|
|||
Remove the unmatched bracket in the --no-clean option's help text.
|
|
@ -1 +0,0 @@
|
|||
Avoid traceback printing on autocomplete after flags in the CLI.
|
|
@ -1 +0,0 @@
|
|||
Fix links to NEWS entry guidelines.
|
|
@ -1 +0,0 @@
|
|||
Fix incorrect parsing of egg names if pip needs to guess the package name.
|
|
@ -1 +0,0 @@
|
|||
Simplify always-true conditions in ``HTMLPage.get_page()``.
|
|
@ -1 +0,0 @@
|
|||
Add unit tests for egg_info_matches.
|
|
@ -1 +0,0 @@
|
|||
Refactor HTMLPage to reduce attributes on it.
|
|
@ -0,0 +1 @@
|
|||
A warning message is emitted when dropping an ``--[extra-]index-url`` value that points to an existing local directory.
|
|
@ -1 +0,0 @@
|
|||
Move static and class methods out of HTMLPage for prepare for refactoring.
|
|
@ -0,0 +1 @@
|
|||
Fix content type detection if a directory named like an archive is used as a package source.
|
|
@ -0,0 +1 @@
|
|||
Fix crashes from unparseable requirements when checking installed packages.
|
|
@ -0,0 +1 @@
|
|||
Fix support for invoking pip using `python src/pip ...`.
|
|
@ -0,0 +1 @@
|
|||
Greatly reduce memory usage when installing wheels containing large files.
|
|
@ -0,0 +1,2 @@
|
|||
Remove the deprecated SVN editable detection based on dependency links
|
||||
during freeze.
|
|
@ -0,0 +1 @@
|
|||
Fix sorting `TypeError` in `move_wheel_files()` when installing some packages.
|
|
@ -0,0 +1 @@
|
|||
Canonicalize sdist file names so they can be matched to a canonicalized package name passed to ``pip install``.
|
|
@ -0,0 +1 @@
|
|||
Remove references to removed #egg=<name>-<version> functionality
|
|
@ -0,0 +1 @@
|
|||
Include the Vendoring Policy in the documentation.
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
Adds hyperlinks to User IRC and Dev IRC in README.
|
|
@ -0,0 +1 @@
|
|||
Percent-decode special characters in SVN URL credentials.
|
|
@ -0,0 +1 @@
|
|||
Add command information in usage document for pip cmd
|
|
@ -1 +0,0 @@
|
|||
Upgrade certifi to 2018.8.24
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1 +0,0 @@
|
|||
Upgrade packaging to 18.0
|
|
@ -1 +0,0 @@
|
|||
Add pep517 version 0.2
|
|
@ -1 +0,0 @@
|
|||
Upgrade pytoml to 0.1.19
|
|
@ -1 +0,0 @@
|
|||
Upgrade pkg_resources to 40.4.3 (via setuptools)
|
|
@ -1 +0,0 @@
|
|||
Fix "Requirements Files" reference in User Guide
|
|
@ -1 +1 @@
|
|||
__version__ = "18.1.dev0"
|
||||
__version__ = "19.0.dev0"
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from sysconfig import get_paths
|
||||
|
||||
from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
|
||||
|
||||
from pip import __file__ as pip_location
|
||||
from pip._internal.utils.misc import call_subprocess
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.ui import open_spinner
|
||||
|
@ -61,6 +63,15 @@ class BuildEnvironment(object):
|
|||
|
||||
os.environ['PYTHONNOUSERSITE'] = '1'
|
||||
|
||||
# Ensure .pth files are honored.
|
||||
with open(os.path.join(purelib, 'sitecustomize.py'), 'w') as fp:
|
||||
fp.write(textwrap.dedent(
|
||||
'''
|
||||
import site
|
||||
site.addsitedir({!r})
|
||||
'''
|
||||
).format(purelib))
|
||||
|
||||
return self.path
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
|
@ -93,8 +104,9 @@ class BuildEnvironment(object):
|
|||
|
||||
def install_requirements(self, finder, requirements, message):
|
||||
args = [
|
||||
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
|
||||
'--no-user', '--prefix', self.path, '--no-warn-script-location',
|
||||
sys.executable, os.path.dirname(pip_location), 'install',
|
||||
'--ignore-installed', '--no-user', '--prefix', self.path,
|
||||
'--no-warn-script-location',
|
||||
]
|
||||
if logger.getEffectiveLevel() <= logging.DEBUG:
|
||||
args.append('-v')
|
||||
|
|
|
@ -12,8 +12,13 @@ from pip._internal.download import path_to_url
|
|||
from pip._internal.models.link import Link
|
||||
from pip._internal.utils.compat import expanduser
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pip._internal.wheel import InvalidWheelFilename, Wheel
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Set, List, Any # noqa: F401
|
||||
from pip._internal.index import FormatControl # noqa: F401
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -29,6 +34,7 @@ class Cache(object):
|
|||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control, allowed_formats):
|
||||
# type: (str, FormatControl, Set[str]) -> None
|
||||
super(Cache, self).__init__()
|
||||
self.cache_dir = expanduser(cache_dir) if cache_dir else None
|
||||
self.format_control = format_control
|
||||
|
@ -38,6 +44,7 @@ class Cache(object):
|
|||
assert self.allowed_formats.union(_valid_formats) == _valid_formats
|
||||
|
||||
def _get_cache_path_parts(self, link):
|
||||
# type: (Link) -> List[str]
|
||||
"""Get parts of part that must be os.path.joined with cache_dir
|
||||
"""
|
||||
|
||||
|
@ -63,6 +70,7 @@ class Cache(object):
|
|||
return parts
|
||||
|
||||
def _get_candidates(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> List[Any]
|
||||
can_not_cache = (
|
||||
not self.cache_dir or
|
||||
not package_name or
|
||||
|
@ -87,23 +95,27 @@ class Cache(object):
|
|||
raise
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
# type: (Link) -> str
|
||||
"""Return a directory to store cached items in for link.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> Link
|
||||
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||
passed link.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _link_for_candidate(self, link, candidate):
|
||||
# type: (Link, str) -> Link
|
||||
root = self.get_path_for_link(link)
|
||||
path = os.path.join(root, candidate)
|
||||
|
||||
return Link(path_to_url(path))
|
||||
|
||||
def cleanup(self):
|
||||
# type: () -> None
|
||||
pass
|
||||
|
||||
|
||||
|
@ -112,11 +124,13 @@ class SimpleWheelCache(Cache):
|
|||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
# type: (str, FormatControl) -> None
|
||||
super(SimpleWheelCache, self).__init__(
|
||||
cache_dir, format_control, {"binary"}
|
||||
)
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
# type: (Link) -> str
|
||||
"""Return a directory to store cached wheels for link
|
||||
|
||||
Because there are M wheels for any one sdist, we provide a directory
|
||||
|
@ -137,6 +151,7 @@ class SimpleWheelCache(Cache):
|
|||
return os.path.join(self.cache_dir, "wheels", *parts)
|
||||
|
||||
def get(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> Link
|
||||
candidates = []
|
||||
|
||||
for wheel_name in self._get_candidates(link, package_name):
|
||||
|
@ -160,6 +175,7 @@ class EphemWheelCache(SimpleWheelCache):
|
|||
"""
|
||||
|
||||
def __init__(self, format_control):
|
||||
# type: (FormatControl) -> None
|
||||
self._temp_dir = TempDirectory(kind="ephem-wheel-cache")
|
||||
self._temp_dir.create()
|
||||
|
||||
|
@ -168,6 +184,7 @@ class EphemWheelCache(SimpleWheelCache):
|
|||
)
|
||||
|
||||
def cleanup(self):
|
||||
# type: () -> None
|
||||
self._temp_dir.cleanup()
|
||||
|
||||
|
||||
|
@ -179,6 +196,7 @@ class WheelCache(Cache):
|
|||
"""
|
||||
|
||||
def __init__(self, cache_dir, format_control):
|
||||
# type: (str, FormatControl) -> None
|
||||
super(WheelCache, self).__init__(
|
||||
cache_dir, format_control, {'binary'}
|
||||
)
|
||||
|
@ -186,17 +204,21 @@ class WheelCache(Cache):
|
|||
self._ephem_cache = EphemWheelCache(format_control)
|
||||
|
||||
def get_path_for_link(self, link):
|
||||
# type: (Link) -> str
|
||||
return self._wheel_cache.get_path_for_link(link)
|
||||
|
||||
def get_ephem_path_for_link(self, link):
|
||||
# type: (Link) -> str
|
||||
return self._ephem_cache.get_path_for_link(link)
|
||||
|
||||
def get(self, link, package_name):
|
||||
# type: (Link, Optional[str]) -> Link
|
||||
retval = self._wheel_cache.get(link, package_name)
|
||||
if retval is link:
|
||||
retval = self._ephem_cache.get(link, package_name)
|
||||
return retval
|
||||
|
||||
def cleanup(self):
|
||||
# type: () -> None
|
||||
self._wheel_cache.cleanup()
|
||||
self._ephem_cache.cleanup()
|
||||
|
|
|
@ -32,7 +32,10 @@ from pip._internal.utils.outdated import pip_version_check
|
|||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional # noqa: F401
|
||||
from typing import Optional, List, Union, Tuple, Any # noqa: F401
|
||||
from optparse import Values # noqa: F401
|
||||
from pip._internal.cache import WheelCache # noqa: F401
|
||||
from pip._internal.req.req_set import RequirementSet # noqa: F401
|
||||
|
||||
__all__ = ['Command']
|
||||
|
||||
|
@ -46,6 +49,7 @@ class Command(object):
|
|||
ignore_require_venv = False # type: bool
|
||||
|
||||
def __init__(self, isolated=False):
|
||||
# type: (bool) -> None
|
||||
parser_kw = {
|
||||
'usage': self.usage,
|
||||
'prog': '%s %s' % (get_prog(), self.name),
|
||||
|
@ -69,7 +73,12 @@ class Command(object):
|
|||
)
|
||||
self.parser.add_option_group(gen_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
# type: (Values, List[Any]) -> Any
|
||||
raise NotImplementedError
|
||||
|
||||
def _build_session(self, options, retries=None, timeout=None):
|
||||
# type: (Values, Optional[int], Optional[int]) -> PipSession
|
||||
session = PipSession(
|
||||
cache=(
|
||||
normalize_path(os.path.join(options.cache_dir, "http"))
|
||||
|
@ -106,10 +115,12 @@ class Command(object):
|
|||
return session
|
||||
|
||||
def parse_args(self, args):
|
||||
# type: (List[str]) -> Tuple
|
||||
# factored out for testability
|
||||
return self.parser.parse_args(args)
|
||||
|
||||
def main(self, args):
|
||||
# type: (List[str]) -> int
|
||||
options, args = self.parse_args(args)
|
||||
|
||||
# Set verbosity so that it can be used elsewhere.
|
||||
|
@ -195,8 +206,15 @@ class Command(object):
|
|||
class RequirementCommand(Command):
|
||||
|
||||
@staticmethod
|
||||
def populate_requirement_set(requirement_set, args, options, finder,
|
||||
session, name, wheel_cache):
|
||||
def populate_requirement_set(requirement_set, # type: RequirementSet
|
||||
args, # type: List[str]
|
||||
options, # type: Values
|
||||
finder, # type: PackageFinder
|
||||
session, # type: PipSession
|
||||
name, # type: str
|
||||
wheel_cache # type: Optional[WheelCache]
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Marshal cmd line args into a requirement set.
|
||||
"""
|
||||
|
@ -251,9 +269,16 @@ class RequirementCommand(Command):
|
|||
'You must give at least one requirement to %(name)s '
|
||||
'(see "pip help %(name)s")' % opts)
|
||||
|
||||
def _build_package_finder(self, options, session,
|
||||
platform=None, python_versions=None,
|
||||
abi=None, implementation=None):
|
||||
def _build_package_finder(
|
||||
self,
|
||||
options, # type: Values
|
||||
session, # type: PipSession
|
||||
platform=None, # type: Optional[str]
|
||||
python_versions=None, # type: Optional[List[str]]
|
||||
abi=None, # type: Optional[str]
|
||||
implementation=None # type: Optional[str]
|
||||
):
|
||||
# type: (...) -> PackageFinder
|
||||
"""
|
||||
Create a package finder appropriate to this requirement command.
|
||||
"""
|
||||
|
|
|
@ -10,6 +10,7 @@ pass on state. To be consistent, all options will follow this design.
|
|||
from __future__ import absolute_import
|
||||
|
||||
import warnings
|
||||
from distutils.util import strtobool
|
||||
from functools import partial
|
||||
from optparse import SUPPRESS_HELP, Option, OptionGroup
|
||||
|
||||
|
@ -22,10 +23,13 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
|||
from pip._internal.utils.ui import BAR_TYPES
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any # noqa: F401
|
||||
from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401
|
||||
from optparse import OptionParser, Values # noqa: F401
|
||||
from pip._internal.cli.parser import ConfigOptionParser # noqa: F401
|
||||
|
||||
|
||||
def make_option_group(group, parser):
|
||||
# type: (Dict[str, Any], ConfigOptionParser) -> OptionGroup
|
||||
"""
|
||||
Return an OptionGroup object
|
||||
group -- assumed to be dict with 'name' and 'options' keys
|
||||
|
@ -38,6 +42,7 @@ def make_option_group(group, parser):
|
|||
|
||||
|
||||
def check_install_build_global(options, check_options=None):
|
||||
# type: (Values, Optional[Values]) -> None
|
||||
"""Disable wheels if per-setup.py call options are set.
|
||||
|
||||
:param options: The OptionParser options to update.
|
||||
|
@ -60,6 +65,7 @@ def check_install_build_global(options, check_options=None):
|
|||
|
||||
|
||||
def check_dist_restriction(options, check_target=False):
|
||||
# type: (Values, bool) -> None
|
||||
"""Function for determining if custom platform options are allowed.
|
||||
|
||||
:param options: The OptionParser options.
|
||||
|
@ -108,7 +114,7 @@ help_ = partial(
|
|||
dest='help',
|
||||
action='help',
|
||||
help='Show help.',
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
isolated_mode = partial(
|
||||
Option,
|
||||
|
@ -120,7 +126,7 @@ isolated_mode = partial(
|
|||
"Run pip in an isolated mode, ignoring environment variables and user "
|
||||
"configuration."
|
||||
),
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
require_virtualenv = partial(
|
||||
Option,
|
||||
|
@ -130,7 +136,7 @@ require_virtualenv = partial(
|
|||
action='store_true',
|
||||
default=False,
|
||||
help=SUPPRESS_HELP
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
verbose = partial(
|
||||
Option,
|
||||
|
@ -139,7 +145,7 @@ verbose = partial(
|
|||
action='count',
|
||||
default=0,
|
||||
help='Give more output. Option is additive, and can be used up to 3 times.'
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
no_color = partial(
|
||||
Option,
|
||||
|
@ -148,7 +154,7 @@ no_color = partial(
|
|||
action='store_true',
|
||||
default=False,
|
||||
help="Suppress colored output",
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
version = partial(
|
||||
Option,
|
||||
|
@ -156,7 +162,7 @@ version = partial(
|
|||
dest='version',
|
||||
action='store_true',
|
||||
help='Show version and exit.',
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
quiet = partial(
|
||||
Option,
|
||||
|
@ -169,7 +175,7 @@ quiet = partial(
|
|||
' times (corresponding to WARNING, ERROR, and CRITICAL logging'
|
||||
' levels).'
|
||||
),
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
progress_bar = partial(
|
||||
Option,
|
||||
|
@ -182,7 +188,7 @@ progress_bar = partial(
|
|||
'Specify type of progress to be displayed [' +
|
||||
'|'.join(BAR_TYPES.keys()) + '] (default: %default)'
|
||||
),
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
log = partial(
|
||||
Option,
|
||||
|
@ -190,7 +196,7 @@ log = partial(
|
|||
dest="log",
|
||||
metavar="path",
|
||||
help="Path to a verbose appending log."
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
no_input = partial(
|
||||
Option,
|
||||
|
@ -200,7 +206,7 @@ no_input = partial(
|
|||
action='store_true',
|
||||
default=False,
|
||||
help=SUPPRESS_HELP
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
proxy = partial(
|
||||
Option,
|
||||
|
@ -209,7 +215,7 @@ proxy = partial(
|
|||
type='str',
|
||||
default='',
|
||||
help="Specify a proxy in the form [user:passwd@]proxy.server:port."
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
retries = partial(
|
||||
Option,
|
||||
|
@ -219,7 +225,7 @@ retries = partial(
|
|||
default=5,
|
||||
help="Maximum number of retries each connection should attempt "
|
||||
"(default %default times).",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
timeout = partial(
|
||||
Option,
|
||||
|
@ -229,7 +235,7 @@ timeout = partial(
|
|||
type='float',
|
||||
default=15,
|
||||
help='Set the socket timeout (default %default seconds).',
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
skip_requirements_regex = partial(
|
||||
Option,
|
||||
|
@ -239,10 +245,11 @@ skip_requirements_regex = partial(
|
|||
type='str',
|
||||
default='',
|
||||
help=SUPPRESS_HELP,
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def exists_action():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
# Option when path already exist
|
||||
'--exists-action',
|
||||
|
@ -264,7 +271,7 @@ cert = partial(
|
|||
type='str',
|
||||
metavar='path',
|
||||
help="Path to alternate CA bundle.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
client_cert = partial(
|
||||
Option,
|
||||
|
@ -275,7 +282,7 @@ client_cert = partial(
|
|||
metavar='path',
|
||||
help="Path to SSL client certificate, a single file containing the "
|
||||
"private key and the certificate in PEM format.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
index_url = partial(
|
||||
Option,
|
||||
|
@ -287,7 +294,7 @@ index_url = partial(
|
|||
"This should point to a repository compliant with PEP 503 "
|
||||
"(the simple repository API) or a local directory laid out "
|
||||
"in the same format.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def extra_index_url():
|
||||
|
@ -310,10 +317,11 @@ no_index = partial(
|
|||
action='store_true',
|
||||
default=False,
|
||||
help='Ignore package index (only looking at --find-links URLs instead).',
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def find_links():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
'-f', '--find-links',
|
||||
dest='find_links',
|
||||
|
@ -327,6 +335,7 @@ def find_links():
|
|||
|
||||
|
||||
def trusted_host():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
"--trusted-host",
|
||||
dest="trusted_hosts",
|
||||
|
@ -346,10 +355,11 @@ process_dependency_links = partial(
|
|||
action="store_true",
|
||||
default=False,
|
||||
help="Enable the processing of dependency links.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def constraints():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
'-c', '--constraint',
|
||||
dest='constraints',
|
||||
|
@ -362,6 +372,7 @@ def constraints():
|
|||
|
||||
|
||||
def requirements():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
'-r', '--requirement',
|
||||
dest='requirements',
|
||||
|
@ -374,6 +385,7 @@ def requirements():
|
|||
|
||||
|
||||
def editable():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
'-e', '--editable',
|
||||
dest='editables',
|
||||
|
@ -394,15 +406,17 @@ src = partial(
|
|||
help='Directory to check out editable projects into. '
|
||||
'The default in a virtualenv is "<venv path>/src". '
|
||||
'The default for global installs is "<current dir>/src".'
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def _get_format_control(values, option):
|
||||
# type: (Values, Option) -> Any
|
||||
"""Get a format_control object."""
|
||||
return getattr(values, option.dest)
|
||||
|
||||
|
||||
def _handle_no_binary(option, opt_str, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
existing = _get_format_control(parser.values, option)
|
||||
FormatControl.handle_mutual_excludes(
|
||||
value, existing.no_binary, existing.only_binary,
|
||||
|
@ -410,6 +424,7 @@ def _handle_no_binary(option, opt_str, value, parser):
|
|||
|
||||
|
||||
def _handle_only_binary(option, opt_str, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
existing = _get_format_control(parser.values, option)
|
||||
FormatControl.handle_mutual_excludes(
|
||||
value, existing.only_binary, existing.no_binary,
|
||||
|
@ -417,6 +432,7 @@ def _handle_only_binary(option, opt_str, value, parser):
|
|||
|
||||
|
||||
def no_binary():
|
||||
# type: () -> Option
|
||||
format_control = FormatControl(set(), set())
|
||||
return Option(
|
||||
"--no-binary", dest="format_control", action="callback",
|
||||
|
@ -432,6 +448,7 @@ def no_binary():
|
|||
|
||||
|
||||
def only_binary():
|
||||
# type: () -> Option
|
||||
format_control = FormatControl(set(), set())
|
||||
return Option(
|
||||
"--only-binary", dest="format_control", action="callback",
|
||||
|
@ -454,7 +471,7 @@ platform = partial(
|
|||
default=None,
|
||||
help=("Only use wheels compatible with <platform>. "
|
||||
"Defaults to the platform of the running system."),
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
python_version = partial(
|
||||
|
@ -469,7 +486,7 @@ python_version = partial(
|
|||
"version (e.g. '2') can be specified to match all "
|
||||
"minor revs of that major version. A minor version "
|
||||
"(e.g. '34') can also be specified."),
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
implementation = partial(
|
||||
|
@ -483,7 +500,7 @@ implementation = partial(
|
|||
" or 'ip'. If not specified, then the current "
|
||||
"interpreter implementation is used. Use 'py' to force "
|
||||
"implementation-agnostic wheels."),
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
abi = partial(
|
||||
|
@ -498,10 +515,11 @@ abi = partial(
|
|||
"you will need to specify --implementation, "
|
||||
"--platform, and --python-version when using "
|
||||
"this option."),
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def prefer_binary():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
"--prefer-binary",
|
||||
dest="prefer_binary",
|
||||
|
@ -518,15 +536,41 @@ cache_dir = partial(
|
|||
default=USER_CACHE_DIR,
|
||||
metavar="dir",
|
||||
help="Store the cache data in <dir>."
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def no_cache_dir_callback(option, opt, value, parser):
|
||||
"""
|
||||
Process a value provided for the --no-cache-dir option.
|
||||
|
||||
This is an optparse.Option callback for the --no-cache-dir option.
|
||||
"""
|
||||
# The value argument will be None if --no-cache-dir is passed via the
|
||||
# command-line, since the option doesn't accept arguments. However,
|
||||
# the value can be non-None if the option is triggered e.g. by an
|
||||
# environment variable, like PIP_NO_CACHE_DIR=true.
|
||||
if value is not None:
|
||||
# Then parse the string value to get argument error-checking.
|
||||
strtobool(value)
|
||||
|
||||
# Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
|
||||
# converted to 0 (like "false" or "no") caused cache_dir to be disabled
|
||||
# rather than enabled (logic would say the latter). Thus, we disable
|
||||
# the cache directory not just on values that parse to True, but (for
|
||||
# backwards compatibility reasons) also on values that parse to False.
|
||||
# In other words, always set it to False if the option is provided in
|
||||
# some (valid) form.
|
||||
parser.values.cache_dir = False
|
||||
|
||||
|
||||
no_cache = partial(
|
||||
Option,
|
||||
"--no-cache-dir",
|
||||
dest="cache_dir",
|
||||
action="store_false",
|
||||
action="callback",
|
||||
callback=no_cache_dir_callback,
|
||||
help="Disable the cache.",
|
||||
)
|
||||
) # type: partial[Option]
|
||||
|
||||
no_deps = partial(
|
||||
Option,
|
||||
|
@ -535,7 +579,7 @@ no_deps = partial(
|
|||
action='store_true',
|
||||
default=False,
|
||||
help="Don't install package dependencies.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
build_dir = partial(
|
||||
Option,
|
||||
|
@ -547,7 +591,7 @@ build_dir = partial(
|
|||
'The location of temporary directories can be controlled by setting '
|
||||
'the TMPDIR environment variable (TEMP on Windows) appropriately. '
|
||||
'When passed, build directories are not cleaned in case of failures.'
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
ignore_requires_python = partial(
|
||||
Option,
|
||||
|
@ -555,7 +599,7 @@ ignore_requires_python = partial(
|
|||
dest='ignore_requires_python',
|
||||
action='store_true',
|
||||
help='Ignore the Requires-Python information.'
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
no_build_isolation = partial(
|
||||
Option,
|
||||
|
@ -566,7 +610,7 @@ no_build_isolation = partial(
|
|||
help='Disable isolation when building a modern source distribution. '
|
||||
'Build dependencies specified by PEP 518 must be already installed '
|
||||
'if this option is used.'
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
install_options = partial(
|
||||
Option,
|
||||
|
@ -579,7 +623,7 @@ install_options = partial(
|
|||
"bin\"). Use multiple --install-option options to pass multiple "
|
||||
"options to setup.py install. If you are using an option with a "
|
||||
"directory path, be sure to use absolute path.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
global_options = partial(
|
||||
Option,
|
||||
|
@ -589,7 +633,7 @@ global_options = partial(
|
|||
metavar='options',
|
||||
help="Extra global options to be supplied to the setup.py "
|
||||
"call before the install command.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
no_clean = partial(
|
||||
Option,
|
||||
|
@ -597,7 +641,7 @@ no_clean = partial(
|
|||
action='store_true',
|
||||
default=False,
|
||||
help="Don't clean up build directories."
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
pre = partial(
|
||||
Option,
|
||||
|
@ -606,7 +650,7 @@ pre = partial(
|
|||
default=False,
|
||||
help="Include pre-release and development versions. By default, "
|
||||
"pip only finds stable versions.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
disable_pip_version_check = partial(
|
||||
Option,
|
||||
|
@ -616,7 +660,7 @@ disable_pip_version_check = partial(
|
|||
default=False,
|
||||
help="Don't periodically check PyPI to determine whether a new version "
|
||||
"of pip is available for download. Implied with --no-index.",
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
# Deprecated, Remove later
|
||||
|
@ -626,10 +670,11 @@ always_unzip = partial(
|
|||
dest='always_unzip',
|
||||
action='store_true',
|
||||
help=SUPPRESS_HELP,
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
def _merge_hash(option, opt_str, value, parser):
|
||||
# type: (Option, str, str, OptionParser) -> None
|
||||
"""Given a value spelled "algo:digest", append the digest to a list
|
||||
pointed to in a dict by the algo name."""
|
||||
if not parser.values.hashes:
|
||||
|
@ -657,7 +702,7 @@ hash = partial(
|
|||
type='string',
|
||||
help="Verify that the package's archive matches this "
|
||||
'hash before installing. Example: --hash=sha256:abcdef...',
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
require_hashes = partial(
|
||||
|
@ -669,7 +714,7 @@ require_hashes = partial(
|
|||
help='Require a hash to check each requirement against, for '
|
||||
'repeatable installs. This option is implied when any package in a '
|
||||
'requirements file has a --hash option.',
|
||||
) # type: Any
|
||||
) # type: partial[Option]
|
||||
|
||||
|
||||
##########
|
||||
|
@ -700,7 +745,7 @@ general_group = {
|
|||
disable_pip_version_check,
|
||||
no_color,
|
||||
]
|
||||
}
|
||||
} # type: Dict[str, Any]
|
||||
|
||||
index_group = {
|
||||
'name': 'Package Index Options',
|
||||
|
@ -711,4 +756,4 @@ index_group = {
|
|||
find_links,
|
||||
process_dependency_links,
|
||||
]
|
||||
}
|
||||
} # type: Dict[str, Any]
|
||||
|
|
|
@ -9,6 +9,7 @@ from distutils.util import strtobool
|
|||
|
||||
from pip._vendor.six import string_types
|
||||
|
||||
from pip._internal.cli.status_codes import UNKNOWN_ERROR
|
||||
from pip._internal.configuration import Configuration, ConfigurationError
|
||||
from pip._internal.utils.compat import get_terminal_size
|
||||
|
||||
|
@ -232,7 +233,7 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
try:
|
||||
self.config.load()
|
||||
except ConfigurationError as err:
|
||||
self.exit(2, err.args[0])
|
||||
self.exit(UNKNOWN_ERROR, str(err))
|
||||
|
||||
defaults = self._update_defaults(self.defaults.copy()) # ours
|
||||
for option in self._get_all_options():
|
||||
|
@ -244,7 +245,7 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
|
||||
def error(self, msg):
|
||||
self.print_usage(sys.stderr)
|
||||
self.exit(2, "%s\n" % msg)
|
||||
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
|
||||
|
||||
|
||||
def invalid_config_error_message(action, key, val):
|
||||
|
|
|
@ -16,7 +16,7 @@ class CheckCommand(Command):
|
|||
summary = 'Verify installed packages have compatible dependencies.'
|
||||
|
||||
def run(self, options, args):
|
||||
package_set = create_package_set_from_installed()
|
||||
package_set, parsing_probs = create_package_set_from_installed()
|
||||
missing, conflicting = check_package_set(package_set)
|
||||
|
||||
for project_name in missing:
|
||||
|
@ -35,7 +35,7 @@ class CheckCommand(Command):
|
|||
project_name, version, req, dep_name, dep_version,
|
||||
)
|
||||
|
||||
if missing or conflicting:
|
||||
if missing or conflicting or parsing_probs:
|
||||
return 1
|
||||
else:
|
||||
logger.info("No broken requirements found.")
|
||||
|
|
|
@ -472,7 +472,11 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
|
||||
def _warn_about_conflicts(self, to_install):
|
||||
package_set, _dep_info = check_install_conflicts(to_install)
|
||||
try:
|
||||
package_set, _dep_info = check_install_conflicts(to_install)
|
||||
except Exception:
|
||||
logger.error("Error checking for conflicts.", exc_info=True)
|
||||
return
|
||||
missing, conflicting = _dep_info
|
||||
|
||||
# NOTE: There is some duplication here from pip check
|
||||
|
|
|
@ -18,7 +18,9 @@ import os
|
|||
from pip._vendor import six
|
||||
from pip._vendor.six.moves import configparser
|
||||
|
||||
from pip._internal.exceptions import ConfigurationError
|
||||
from pip._internal.exceptions import (
|
||||
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
|
||||
)
|
||||
from pip._internal.locations import (
|
||||
legacy_config_file, new_config_file, running_under_virtualenv,
|
||||
site_config_files, venv_config_file,
|
||||
|
@ -289,11 +291,16 @@ class Configuration(object):
|
|||
try:
|
||||
parser.read(fname)
|
||||
except UnicodeDecodeError:
|
||||
raise ConfigurationError((
|
||||
"ERROR: "
|
||||
"Configuration file contains invalid %s characters.\n"
|
||||
"Please fix your configuration, located at %s\n"
|
||||
) % (locale.getpreferredencoding(False), fname))
|
||||
# See https://github.com/pypa/pip/issues/4963
|
||||
raise ConfigurationFileCouldNotBeLoaded(
|
||||
reason="contains invalid {} characters".format(
|
||||
locale.getpreferredencoding(False)
|
||||
),
|
||||
fname=fname,
|
||||
)
|
||||
except configparser.Error as error:
|
||||
# See https://github.com/pypa/pip/issues/4893
|
||||
raise ConfigurationFileCouldNotBeLoaded(error=error)
|
||||
return parser
|
||||
|
||||
def _load_environment_vars(self):
|
||||
|
|
|
@ -26,7 +26,6 @@ from pip._vendor.requests.utils import get_netrc_auth
|
|||
from pip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip._vendor.six.moves.urllib import request as urllib_request
|
||||
from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
|
||||
from pip._vendor.urllib3.util import IS_PYOPENSSL
|
||||
|
||||
import pip
|
||||
|
@ -39,14 +38,18 @@ from pip._internal.utils.glibc import libc_ver
|
|||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import (
|
||||
ARCHIVE_EXTENSIONS, ask_path_exists, backup_dir, call_subprocess, consume,
|
||||
display_path, format_size, get_installed_version, rmtree, splitext,
|
||||
unpack_file,
|
||||
display_path, format_size, get_installed_version, rmtree,
|
||||
split_auth_from_netloc, splitext, unpack_file,
|
||||
)
|
||||
from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pip._internal.utils.ui import DownloadProgressProvider
|
||||
from pip._internal.vcs import vcs
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional # noqa: F401
|
||||
|
||||
try:
|
||||
import ssl # noqa
|
||||
except ImportError:
|
||||
|
@ -142,8 +145,8 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
def __call__(self, req):
|
||||
parsed = urllib_parse.urlparse(req.url)
|
||||
|
||||
# Get the netloc without any embedded credentials
|
||||
netloc = parsed.netloc.rsplit("@", 1)[-1]
|
||||
# Split the credentials from the netloc.
|
||||
netloc, url_user_password = split_auth_from_netloc(parsed.netloc)
|
||||
|
||||
# Set the url of the request to the url without any credentials
|
||||
req.url = urllib_parse.urlunparse(parsed[:1] + (netloc,) + parsed[2:])
|
||||
|
@ -151,9 +154,9 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
# Use any stored credentials that we have for this netloc
|
||||
username, password = self.passwords.get(netloc, (None, None))
|
||||
|
||||
# Extract credentials embedded in the url if we have none stored
|
||||
# Use the credentials embedded in the url if we have none stored
|
||||
if username is None:
|
||||
username, password = self.parse_credentials(parsed.netloc)
|
||||
username, password = url_user_password
|
||||
|
||||
# Get creds from netrc if we still don't have them
|
||||
if username is None and password is None:
|
||||
|
@ -199,6 +202,7 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
|
||||
# Add our new username and password to the request
|
||||
req = HTTPBasicAuth(username or "", password or "")(resp.request)
|
||||
req.register_hook("response", self.warn_on_401)
|
||||
|
||||
# Send our new request
|
||||
new_resp = resp.connection.send(req, **kwargs)
|
||||
|
@ -206,14 +210,11 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
|
||||
return new_resp
|
||||
|
||||
def parse_credentials(self, netloc):
|
||||
if "@" in netloc:
|
||||
userinfo = netloc.rsplit("@", 1)[0]
|
||||
if ":" in userinfo:
|
||||
user, pwd = userinfo.split(":", 1)
|
||||
return (urllib_unquote(user), urllib_unquote(pwd))
|
||||
return urllib_unquote(userinfo), None
|
||||
return None, None
|
||||
def warn_on_401(self, resp, **kwargs):
|
||||
# warn user that they provided incorrect credentials
|
||||
if resp.status_code == 401:
|
||||
logger.warning('401 Error, Credentials not correct for %s',
|
||||
resp.request.url)
|
||||
|
||||
|
||||
class LocalFSAdapter(BaseAdapter):
|
||||
|
@ -324,7 +325,7 @@ class InsecureHTTPAdapter(HTTPAdapter):
|
|||
|
||||
class PipSession(requests.Session):
|
||||
|
||||
timeout = None
|
||||
timeout = None # type: Optional[int]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
retries = kwargs.pop("retries", 0)
|
||||
|
|
|
@ -247,3 +247,22 @@ class HashMismatch(HashError):
|
|||
class UnsupportedPythonVersion(InstallationError):
|
||||
"""Unsupported python version according to Requires-Python package
|
||||
metadata."""
|
||||
|
||||
|
||||
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
|
||||
"""When there are errors while loading a configuration file
|
||||
"""
|
||||
|
||||
def __init__(self, reason="could not be loaded", fname=None, error=None):
|
||||
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
|
||||
self.reason = reason
|
||||
self.fname = fname
|
||||
self.error = error
|
||||
|
||||
def __str__(self):
|
||||
if self.fname is not None:
|
||||
message_part = " in {}.".format(self.fname)
|
||||
else:
|
||||
assert self.error is not None
|
||||
message_part = ".\n{}\n".format(self.error.message)
|
||||
return "Configuration file {}{}".format(self.reason, message_part)
|
||||
|
|
|
@ -16,7 +16,7 @@ from pip._vendor.distlib.compat import unescape
|
|||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
from pip._vendor.requests.exceptions import SSLError
|
||||
from pip._vendor.requests.exceptions import RetryError, SSLError
|
||||
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
from pip._vendor.six.moves.urllib import request as urllib_request
|
||||
|
||||
|
@ -35,7 +35,7 @@ from pip._internal.utils.deprecation import deprecated
|
|||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import (
|
||||
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, normalize_path,
|
||||
remove_auth_from_url,
|
||||
redact_password_from_url,
|
||||
)
|
||||
from pip._internal.utils.packaging import check_requires_python
|
||||
from pip._internal.wheel import Wheel, wheel_ext
|
||||
|
@ -59,18 +59,113 @@ SECURE_ORIGINS = [
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_content_type(url, session):
|
||||
"""Get the Content-Type of the given url, using a HEAD request"""
|
||||
def _match_vcs_scheme(url):
|
||||
"""Look for VCS schemes in the URL.
|
||||
|
||||
Returns the matched VCS scheme, or None if there's no match.
|
||||
"""
|
||||
from pip._internal.vcs import VcsSupport
|
||||
for scheme in VcsSupport.schemes:
|
||||
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
|
||||
return scheme
|
||||
return None
|
||||
|
||||
|
||||
def _is_url_like_archive(url):
|
||||
"""Return whether the URL looks like an archive.
|
||||
"""
|
||||
filename = Link(url).filename
|
||||
for bad_ext in ARCHIVE_EXTENSIONS:
|
||||
if filename.endswith(bad_ext):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _NotHTML(Exception):
|
||||
def __init__(self, content_type, request_desc):
|
||||
super(_NotHTML, self).__init__(content_type, request_desc)
|
||||
self.content_type = content_type
|
||||
self.request_desc = request_desc
|
||||
|
||||
|
||||
def _ensure_html_header(response):
|
||||
"""Check the Content-Type header to ensure the response contains HTML.
|
||||
|
||||
Raises `_NotHTML` if the content type is not text/html.
|
||||
"""
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if not content_type.lower().startswith("text/html"):
|
||||
raise _NotHTML(content_type, response.request.method)
|
||||
|
||||
|
||||
class _NotHTTP(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _ensure_html_response(url, session):
|
||||
"""Send a HEAD request to the URL, and ensure the response contains HTML.
|
||||
|
||||
Raises `_NotHTTP` if the URL is not available for a HEAD request, or
|
||||
`_NotHTML` if the content type is not text/html.
|
||||
"""
|
||||
scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url)
|
||||
if scheme not in {'http', 'https'}:
|
||||
# FIXME: some warning or something?
|
||||
# assertion error?
|
||||
return ''
|
||||
raise _NotHTTP()
|
||||
|
||||
resp = session.head(url, allow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
|
||||
return resp.headers.get("Content-Type", "")
|
||||
_ensure_html_header(resp)
|
||||
|
||||
|
||||
def _get_html_response(url, session):
|
||||
"""Access an HTML page with GET, and return the response.
|
||||
|
||||
This consists of three parts:
|
||||
|
||||
1. If the URL looks suspiciously like an archive, send a HEAD first to
|
||||
check the Content-Type is HTML, to avoid downloading a large file.
|
||||
Raise `_NotHTTP` if the content type cannot be determined, or
|
||||
`_NotHTML` if it is not HTML.
|
||||
2. Actually perform the request. Raise HTTP exceptions on network failures.
|
||||
3. Check the Content-Type header to make sure we got HTML, and raise
|
||||
`_NotHTML` otherwise.
|
||||
"""
|
||||
if _is_url_like_archive(url):
|
||||
_ensure_html_response(url, session=session)
|
||||
|
||||
logger.debug('Getting page %s', url)
|
||||
|
||||
resp = session.get(
|
||||
url,
|
||||
headers={
|
||||
"Accept": "text/html",
|
||||
# We don't want to blindly returned cached data for
|
||||
# /simple/, because authors generally expecting that
|
||||
# twine upload && pip install will function, but if
|
||||
# they've done a pip install in the last ~10 minutes
|
||||
# it won't. Thus by setting this to zero we will not
|
||||
# blindly use any cached data, however the benefit of
|
||||
# using max-age=0 instead of no-cache, is that we will
|
||||
# still support conditional requests, so we will still
|
||||
# minimize traffic sent in cases where the page hasn't
|
||||
# changed at all, we will just always incur the round
|
||||
# trip for the conditional GET now instead of only
|
||||
# once per 10 minutes.
|
||||
# For more information, please see pypa/pip#5670.
|
||||
"Cache-Control": "max-age=0",
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
# The check for archives above only works if the url ends with
|
||||
# something that looks like an archive. However that is not a
|
||||
# requirement of an url. Unless we issue a HEAD request on every
|
||||
# url we cannot know ahead of time for sure if something is HTML
|
||||
# or not. However we can check after we've downloaded it.
|
||||
_ensure_html_header(resp)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def _handle_get_page_fail(link, reason, url, meth=None):
|
||||
|
@ -85,84 +180,40 @@ def _get_html_page(link, session=None):
|
|||
"_get_html_page() missing 1 required keyword argument: 'session'"
|
||||
)
|
||||
|
||||
url = link.url
|
||||
url = url.split('#', 1)[0]
|
||||
url = link.url.split('#', 1)[0]
|
||||
|
||||
# Check for VCS schemes that do not support lookup as web pages.
|
||||
from pip._internal.vcs import VcsSupport
|
||||
for scheme in VcsSupport.schemes:
|
||||
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
|
||||
logger.debug('Cannot look at %s URL %s', scheme, link)
|
||||
return None
|
||||
vcs_scheme = _match_vcs_scheme(url)
|
||||
if vcs_scheme:
|
||||
logger.debug('Cannot look at %s URL %s', vcs_scheme, link)
|
||||
return None
|
||||
|
||||
# Tack index.html onto file:// URLs that point to directories
|
||||
scheme, _, path, _, _, _ = urllib_parse.urlparse(url)
|
||||
if (scheme == 'file' and os.path.isdir(urllib_request.url2pathname(path))):
|
||||
# add trailing slash if not present so urljoin doesn't trim
|
||||
# final segment
|
||||
if not url.endswith('/'):
|
||||
url += '/'
|
||||
url = urllib_parse.urljoin(url, 'index.html')
|
||||
logger.debug(' file: URL is directory, getting %s', url)
|
||||
|
||||
try:
|
||||
filename = link.filename
|
||||
for bad_ext in ARCHIVE_EXTENSIONS:
|
||||
if filename.endswith(bad_ext):
|
||||
content_type = _get_content_type(url, session=session)
|
||||
if content_type.lower().startswith('text/html'):
|
||||
break
|
||||
else:
|
||||
logger.debug(
|
||||
'Skipping page %s because of Content-Type: %s',
|
||||
link,
|
||||
content_type,
|
||||
)
|
||||
return
|
||||
|
||||
logger.debug('Getting page %s', url)
|
||||
|
||||
# Tack index.html onto file:// URLs that point to directories
|
||||
(scheme, netloc, path, params, query, fragment) = \
|
||||
urllib_parse.urlparse(url)
|
||||
if (scheme == 'file' and
|
||||
os.path.isdir(urllib_request.url2pathname(path))):
|
||||
# add trailing slash if not present so urljoin doesn't trim
|
||||
# final segment
|
||||
if not url.endswith('/'):
|
||||
url += '/'
|
||||
url = urllib_parse.urljoin(url, 'index.html')
|
||||
logger.debug(' file: URL is directory, getting %s', url)
|
||||
|
||||
resp = session.get(
|
||||
url,
|
||||
headers={
|
||||
"Accept": "text/html",
|
||||
# We don't want to blindly returned cached data for
|
||||
# /simple/, because authors generally expecting that
|
||||
# twine upload && pip install will function, but if
|
||||
# they've done a pip install in the last ~10 minutes
|
||||
# it won't. Thus by setting this to zero we will not
|
||||
# blindly use any cached data, however the benefit of
|
||||
# using max-age=0 instead of no-cache, is that we will
|
||||
# still support conditional requests, so we will still
|
||||
# minimize traffic sent in cases where the page hasn't
|
||||
# changed at all, we will just always incur the round
|
||||
# trip for the conditional GET now instead of only
|
||||
# once per 10 minutes.
|
||||
# For more information, please see pypa/pip#5670.
|
||||
"Cache-Control": "max-age=0",
|
||||
},
|
||||
resp = _get_html_response(url, session=session)
|
||||
except _NotHTTP as exc:
|
||||
logger.debug(
|
||||
'Skipping page %s because it looks like an archive, and cannot '
|
||||
'be checked by HEAD.', link,
|
||||
)
|
||||
except _NotHTML as exc:
|
||||
logger.debug(
|
||||
'Skipping page %s because the %s request got Content-Type: %s',
|
||||
link, exc.request_desc, exc.content_type,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
# The check for archives above only works if the url ends with
|
||||
# something that looks like an archive. However that is not a
|
||||
# requirement of an url. Unless we issue a HEAD request on every
|
||||
# url we cannot know ahead of time for sure if something is HTML
|
||||
# or not. However we can check after we've downloaded it.
|
||||
content_type = resp.headers.get('Content-Type', 'unknown')
|
||||
if not content_type.lower().startswith("text/html"):
|
||||
logger.debug(
|
||||
'Skipping page %s because of Content-Type: %s',
|
||||
link,
|
||||
content_type,
|
||||
)
|
||||
return
|
||||
|
||||
inst = HTMLPage(resp.content, resp.url, resp.headers)
|
||||
except requests.HTTPError as exc:
|
||||
_handle_get_page_fail(link, exc, url)
|
||||
except RetryError as exc:
|
||||
_handle_get_page_fail(link, exc, url)
|
||||
except SSLError as exc:
|
||||
reason = "There was a problem confirming the ssl certificate: "
|
||||
reason += str(exc)
|
||||
|
@ -172,7 +223,7 @@ def _get_html_page(link, session=None):
|
|||
except requests.Timeout:
|
||||
_handle_get_page_fail(link, "timed out", url)
|
||||
else:
|
||||
return inst
|
||||
return HTMLPage(resp.content, resp.url, resp.headers)
|
||||
|
||||
|
||||
class PackageFinder(object):
|
||||
|
@ -275,7 +326,7 @@ class PackageFinder(object):
|
|||
if self.index_urls and self.index_urls != [PyPI.simple_url]:
|
||||
lines.append(
|
||||
"Looking in indexes: {}".format(", ".join(
|
||||
remove_auth_from_url(url) for url in self.index_urls))
|
||||
redact_password_from_url(url) for url in self.index_urls))
|
||||
)
|
||||
if self.find_links:
|
||||
lines.append(
|
||||
|
@ -293,7 +344,7 @@ class PackageFinder(object):
|
|||
"Dependency Links processing has been deprecated and will be "
|
||||
"removed in a future release.",
|
||||
replacement="PEP 508 URL dependencies",
|
||||
gone_in="18.2",
|
||||
gone_in="19.0",
|
||||
issue=4187,
|
||||
)
|
||||
self.dependency_links.extend(links)
|
||||
|
@ -332,6 +383,11 @@ class PackageFinder(object):
|
|||
sort_path(os.path.join(path, item))
|
||||
elif is_file_url:
|
||||
urls.append(url)
|
||||
else:
|
||||
logger.warning(
|
||||
"Path '{0}' is ignored: "
|
||||
"it is a directory.".format(path),
|
||||
)
|
||||
elif os.path.isfile(path):
|
||||
sort_path(path)
|
||||
else:
|
||||
|
@ -672,7 +728,7 @@ class PackageFinder(object):
|
|||
continue
|
||||
seen.add(location)
|
||||
|
||||
page = self._get_page(location)
|
||||
page = _get_html_page(location, session=self.session)
|
||||
if page is None:
|
||||
continue
|
||||
|
||||
|
@ -759,8 +815,8 @@ class PackageFinder(object):
|
|||
return
|
||||
|
||||
if not version:
|
||||
version = egg_info_matches(egg_info, search.supplied, link)
|
||||
if version is None:
|
||||
version = _egg_info_matches(egg_info, search.canonical)
|
||||
if not version:
|
||||
self._log_skipped_link(
|
||||
link, 'Missing project version for %s' % search.supplied)
|
||||
return
|
||||
|
@ -781,45 +837,55 @@ class PackageFinder(object):
|
|||
support_this_python = True
|
||||
|
||||
if not support_this_python:
|
||||
logger.debug("The package %s is incompatible with the python"
|
||||
"version in use. Acceptable python versions are:%s",
|
||||
logger.debug("The package %s is incompatible with the python "
|
||||
"version in use. Acceptable python versions are: %s",
|
||||
link, link.requires_python)
|
||||
return
|
||||
logger.debug('Found link %s, version: %s', link, version)
|
||||
|
||||
return InstallationCandidate(search.supplied, version, link)
|
||||
|
||||
def _get_page(self, link):
|
||||
return _get_html_page(link, session=self.session)
|
||||
|
||||
def _find_name_version_sep(egg_info, canonical_name):
|
||||
"""Find the separator's index based on the package's canonical name.
|
||||
|
||||
`egg_info` must be an egg info string for the given package, and
|
||||
`canonical_name` must be the package's canonical name.
|
||||
|
||||
This function is needed since the canonicalized name does not necessarily
|
||||
have the same length as the egg info's name part. An example::
|
||||
|
||||
>>> egg_info = 'foo__bar-1.0'
|
||||
>>> canonical_name = 'foo-bar'
|
||||
>>> _find_name_version_sep(egg_info, canonical_name)
|
||||
8
|
||||
"""
|
||||
# Project name and version must be separated by one single dash. Find all
|
||||
# occurrences of dashes; if the string in front of it matches the canonical
|
||||
# name, this is the one separating the name and version parts.
|
||||
for i, c in enumerate(egg_info):
|
||||
if c != "-":
|
||||
continue
|
||||
if canonicalize_name(egg_info[:i]) == canonical_name:
|
||||
return i
|
||||
raise ValueError("{} does not match {}".format(egg_info, canonical_name))
|
||||
|
||||
|
||||
def egg_info_matches(
|
||||
egg_info, search_name, link,
|
||||
_egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)):
|
||||
def _egg_info_matches(egg_info, canonical_name):
|
||||
"""Pull the version part out of a string.
|
||||
|
||||
:param egg_info: The string to parse. E.g. foo-2.1
|
||||
:param search_name: The name of the package this belongs to. None to
|
||||
infer the name. Note that this cannot unambiguously parse strings
|
||||
like foo-2-2 which might be foo, 2-2 or foo-2, 2.
|
||||
:param link: The link the string came from, for logging on failure.
|
||||
:param canonical_name: The canonicalized name of the package this
|
||||
belongs to.
|
||||
"""
|
||||
match = _egg_info_re.search(egg_info)
|
||||
if not match:
|
||||
logger.debug('Could not parse version from link: %s', link)
|
||||
try:
|
||||
version_start = _find_name_version_sep(egg_info, canonical_name) + 1
|
||||
except ValueError:
|
||||
return None
|
||||
if search_name is None:
|
||||
full_match = match.group(0)
|
||||
return full_match.split('-', 1)[-1]
|
||||
name = match.group(0).lower()
|
||||
# To match the "safe" name that pkg_resources creates:
|
||||
name = name.replace('_', '-')
|
||||
# project name and version must be separated by a dash
|
||||
look_for = search_name.lower() + "-"
|
||||
if name.startswith(look_for):
|
||||
return match.group(0)[len(look_for):]
|
||||
else:
|
||||
version = egg_info[version_start:]
|
||||
if not version:
|
||||
return None
|
||||
return version
|
||||
|
||||
|
||||
def _determine_base_url(document, page_url):
|
||||
|
@ -870,7 +936,7 @@ class HTMLPage(object):
|
|||
self.headers = headers
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
return redact_password_from_url(self.url)
|
||||
|
||||
def iter_links(self):
|
||||
"""Yields all links in the page"""
|
||||
|
|
|
@ -12,6 +12,11 @@ from distutils.command.install import SCHEME_KEYS # type: ignore
|
|||
|
||||
from pip._internal.utils import appdirs
|
||||
from pip._internal.utils.compat import WINDOWS, expanduser
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Union, Dict, List, Optional # noqa: F401
|
||||
|
||||
|
||||
# Application Directories
|
||||
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
|
||||
|
@ -28,6 +33,7 @@ PIP_DELETE_MARKER_FILENAME = 'pip-delete-this-directory.txt'
|
|||
|
||||
|
||||
def write_delete_marker_file(directory):
|
||||
# type: (str) -> None
|
||||
"""
|
||||
Write the pip delete marker file into this directory.
|
||||
"""
|
||||
|
@ -37,6 +43,7 @@ def write_delete_marker_file(directory):
|
|||
|
||||
|
||||
def running_under_virtualenv():
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return True if we're running inside a virtualenv, False otherwise.
|
||||
|
||||
|
@ -50,6 +57,7 @@ def running_under_virtualenv():
|
|||
|
||||
|
||||
def virtualenv_no_global():
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return True if in a venv and no system site packages.
|
||||
"""
|
||||
|
@ -59,6 +67,8 @@ def virtualenv_no_global():
|
|||
no_global_file = os.path.join(site_mod_dir, 'no-global-site-packages.txt')
|
||||
if running_under_virtualenv() and os.path.isfile(no_global_file):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if running_under_virtualenv():
|
||||
|
@ -80,7 +90,8 @@ src_prefix = os.path.abspath(src_prefix)
|
|||
|
||||
# FIXME doesn't account for venv linked to global site-packages
|
||||
|
||||
site_packages = sysconfig.get_path("purelib")
|
||||
site_packages = sysconfig.get_path("purelib") # type: Optional[str]
|
||||
|
||||
# This is because of a bug in PyPy's sysconfig module, see
|
||||
# https://bitbucket.org/pypy/pypy/issues/2506/sysconfig-returns-incorrect-paths
|
||||
# for more information.
|
||||
|
@ -135,6 +146,7 @@ new_config_file = os.path.join(appdirs.user_config_dir("pip"), config_basename)
|
|||
|
||||
def distutils_scheme(dist_name, user=False, home=None, root=None,
|
||||
isolated=False, prefix=None):
|
||||
# type:(str, bool, str, str, bool, str) -> dict
|
||||
"""
|
||||
Return a distutils install scheme
|
||||
"""
|
||||
|
@ -146,12 +158,13 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
|
|||
extra_dist_args = {"script_args": ["--no-user-cfg"]}
|
||||
else:
|
||||
extra_dist_args = {}
|
||||
dist_args = {'name': dist_name}
|
||||
dist_args = {'name': dist_name} # type: Dict[str, Union[str, List[str]]]
|
||||
dist_args.update(extra_dist_args)
|
||||
|
||||
d = Distribution(dist_args)
|
||||
d.parse_config_files()
|
||||
i = d.get_command_obj('install', create=True)
|
||||
# Ignoring, typeshed issue reported python/typeshed/issues/2567
|
||||
d.parse_config_files() # type: ignore
|
||||
i = d.get_command_obj('install', create=True) # type: ignore
|
||||
# NOTE: setting user or home has the side-effect of creating the home dir
|
||||
# or user base for installations during finalize_options()
|
||||
# ideally, we'd prefer a scheme class that has no side-effects.
|
||||
|
@ -171,7 +184,9 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
|
|||
# platlib). Note, i.install_lib is *always* set after
|
||||
# finalize_options(); we only want to override here if the user
|
||||
# has explicitly requested it hence going back to the config
|
||||
if 'install_lib' in d.get_option_dict('install'):
|
||||
|
||||
# Ignoring, typeshed issue reported python/typeshed/issues/2567
|
||||
if 'install_lib' in d.get_option_dict('install'): # type: ignore
|
||||
scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib))
|
||||
|
||||
if running_under_virtualenv():
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Set # noqa: F401
|
||||
|
||||
|
||||
class FormatControl(object):
|
||||
|
@ -11,6 +15,7 @@ class FormatControl(object):
|
|||
are listed, with any given package only showing up in one field at a time.
|
||||
"""
|
||||
def __init__(self, no_binary=None, only_binary=None):
|
||||
# type: (Optional[Set], Optional[Set]) -> None
|
||||
self.no_binary = set() if no_binary is None else no_binary
|
||||
self.only_binary = set() if only_binary is None else only_binary
|
||||
|
||||
|
@ -29,6 +34,7 @@ class FormatControl(object):
|
|||
|
||||
@staticmethod
|
||||
def handle_mutual_excludes(value, target, other):
|
||||
# type: (str, Optional[Set], Optional[Set]) -> None
|
||||
if value.startswith('-'):
|
||||
raise CommandError(
|
||||
"--no-binary / --only-binary option requires 1 argument."
|
||||
|
|
|
@ -4,7 +4,7 @@ import re
|
|||
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
||||
|
||||
from pip._internal.download import path_to_url
|
||||
from pip._internal.utils.misc import splitext
|
||||
from pip._internal.utils.misc import redact_password_from_url, splitext
|
||||
from pip._internal.utils.models import KeyBasedCompareMixin
|
||||
from pip._internal.wheel import wheel_ext
|
||||
|
||||
|
@ -44,9 +44,10 @@ class Link(KeyBasedCompareMixin):
|
|||
else:
|
||||
rp = ''
|
||||
if self.comes_from:
|
||||
return '%s (from %s)%s' % (self.url, self.comes_from, rp)
|
||||
return '%s (from %s)%s' % (redact_password_from_url(self.url),
|
||||
self.comes_from, rp)
|
||||
else:
|
||||
return str(self.url)
|
||||
return redact_password_from_url(str(self.url))
|
||||
|
||||
def __repr__(self):
|
||||
return '<Link %s>' % self
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
"""Validation of dependencies of packages
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
from pip._internal.operations.prepare import make_abstract_dist
|
||||
from pip._internal.utils.misc import get_installed_distributions
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from pip._internal.req.req_install import InstallRequirement # noqa: F401
|
||||
from typing import ( # noqa: F401
|
||||
|
@ -28,7 +32,7 @@ PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])
|
|||
|
||||
|
||||
def create_package_set_from_installed(**kwargs):
|
||||
# type: (**Any) -> PackageSet
|
||||
# type: (**Any) -> Tuple[PackageSet, bool]
|
||||
"""Converts a list of distributions into a PackageSet.
|
||||
"""
|
||||
# Default to using all packages installed on the system
|
||||
|
@ -36,10 +40,16 @@ def create_package_set_from_installed(**kwargs):
|
|||
kwargs = {"local_only": False, "skip": ()}
|
||||
|
||||
package_set = {}
|
||||
problems = False
|
||||
for dist in get_installed_distributions(**kwargs):
|
||||
name = canonicalize_name(dist.project_name)
|
||||
package_set[name] = PackageDetails(dist.version, dist.requires())
|
||||
return package_set
|
||||
try:
|
||||
package_set[name] = PackageDetails(dist.version, dist.requires())
|
||||
except RequirementParseError as e:
|
||||
# Don't crash on broken metadata
|
||||
logging.warning("Error parsing requirements for %s: %s", name, e)
|
||||
problems = True
|
||||
return package_set, problems
|
||||
|
||||
|
||||
def check_package_set(package_set, should_ignore=None):
|
||||
|
@ -95,7 +105,7 @@ def check_install_conflicts(to_install):
|
|||
installing given requirements
|
||||
"""
|
||||
# Start from the current state
|
||||
package_set = create_package_set_from_installed()
|
||||
package_set, _ = create_package_set_from_installed()
|
||||
# Install packages
|
||||
would_be_installed = _simulate_installation_of(to_install, package_set)
|
||||
|
||||
|
|
|
@ -5,18 +5,17 @@ import logging
|
|||
import os
|
||||
import re
|
||||
|
||||
from pip._vendor import pkg_resources, six
|
||||
from pip._vendor import six
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import BadCommand, InstallationError
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_editable, install_req_from_line,
|
||||
)
|
||||
from pip._internal.req.req_file import COMMENT_RE
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
from pip._internal.utils.misc import (
|
||||
dist_is_editable, get_installed_distributions, make_vcs_requirement_url,
|
||||
dist_is_editable, get_installed_distributions,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -35,16 +34,6 @@ def freeze(
|
|||
if skip_regex:
|
||||
skip_match = re.compile(skip_regex).search
|
||||
|
||||
dependency_links = []
|
||||
|
||||
for dist in pkg_resources.working_set:
|
||||
if dist.has_metadata('dependency_links.txt'):
|
||||
dependency_links.extend(
|
||||
dist.get_metadata_lines('dependency_links.txt')
|
||||
)
|
||||
for link in find_links:
|
||||
if '#egg=' in link:
|
||||
dependency_links.append(link)
|
||||
for link in find_links:
|
||||
yield '-f %s' % link
|
||||
installations = {}
|
||||
|
@ -52,10 +41,7 @@ def freeze(
|
|||
skip=(),
|
||||
user_only=user_only):
|
||||
try:
|
||||
req = FrozenRequirement.from_dist(
|
||||
dist,
|
||||
dependency_links
|
||||
)
|
||||
req = FrozenRequirement.from_dist(dist)
|
||||
except RequirementParseError:
|
||||
logger.warning(
|
||||
"Could not parse requirement: %s",
|
||||
|
@ -128,10 +114,10 @@ def freeze(
|
|||
# but has been processed already
|
||||
if not req_files[line_req.name]:
|
||||
logger.warning(
|
||||
"Requirement file [%s] contains %s, but that "
|
||||
"package is not installed",
|
||||
"Requirement file [%s] contains %s, but "
|
||||
"package %r is not installed",
|
||||
req_file_path,
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
COMMENT_RE.sub('', line).strip(), line_req.name
|
||||
)
|
||||
else:
|
||||
req_files[line_req.name].append(req_file_path)
|
||||
|
@ -157,6 +143,58 @@ def freeze(
|
|||
yield str(installation).rstrip()
|
||||
|
||||
|
||||
def get_requirement_info(dist):
|
||||
"""
|
||||
Compute and return values (req, editable, comments) for use in
|
||||
FrozenRequirement.from_dist().
|
||||
"""
|
||||
if not dist_is_editable(dist):
|
||||
return (None, False, [])
|
||||
|
||||
location = os.path.normcase(os.path.abspath(dist.location))
|
||||
|
||||
from pip._internal.vcs import vcs
|
||||
vc_type = vcs.get_backend_type(location)
|
||||
|
||||
if not vc_type:
|
||||
req = dist.as_requirement()
|
||||
logger.debug(
|
||||
'No VCS found for editable requirement {!r} in: {!r}', req,
|
||||
location,
|
||||
)
|
||||
comments = [
|
||||
'# Editable, no version control detected ({})'.format(req)
|
||||
]
|
||||
return (location, True, comments)
|
||||
|
||||
try:
|
||||
req = vc_type().get_src_requirement(dist, location)
|
||||
except BadCommand:
|
||||
logger.warning(
|
||||
'cannot determine version of editable source in %s '
|
||||
'(%s command not found in path)',
|
||||
location,
|
||||
vc_type.name,
|
||||
)
|
||||
return (None, True, [])
|
||||
|
||||
except InstallationError as exc:
|
||||
logger.warning(
|
||||
"Error when trying to get requirement for VCS system %s, "
|
||||
"falling back to uneditable format", exc
|
||||
)
|
||||
else:
|
||||
if req is not None:
|
||||
return (req, True, [])
|
||||
|
||||
logger.warning(
|
||||
'Could not determine repository location of %s', location
|
||||
)
|
||||
comments = ['## !! Could not determine repository location']
|
||||
|
||||
return (None, False, comments)
|
||||
|
||||
|
||||
class FrozenRequirement(object):
|
||||
def __init__(self, name, req, editable, comments=()):
|
||||
self.name = name
|
||||
|
@ -164,98 +202,13 @@ class FrozenRequirement(object):
|
|||
self.editable = editable
|
||||
self.comments = comments
|
||||
|
||||
_rev_re = re.compile(r'-r(\d+)$')
|
||||
_date_re = re.compile(r'-(20\d\d\d\d\d\d)$')
|
||||
|
||||
@classmethod
|
||||
def _init_args_from_dist(cls, dist, dependency_links):
|
||||
"""
|
||||
Compute and return arguments (req, editable, comments) to pass to
|
||||
FrozenRequirement.__init__().
|
||||
|
||||
This method is for use in FrozenRequirement.from_dist().
|
||||
"""
|
||||
location = os.path.normcase(os.path.abspath(dist.location))
|
||||
comments = []
|
||||
from pip._internal.vcs import vcs, get_src_requirement
|
||||
if dist_is_editable(dist) and vcs.get_backend_name(location):
|
||||
editable = True
|
||||
try:
|
||||
req = get_src_requirement(dist, location)
|
||||
except InstallationError as exc:
|
||||
logger.warning(
|
||||
"Error when trying to get requirement for VCS system %s, "
|
||||
"falling back to uneditable format", exc
|
||||
)
|
||||
req = None
|
||||
if req is None:
|
||||
logger.warning(
|
||||
'Could not determine repository location of %s', location
|
||||
)
|
||||
comments.append(
|
||||
'## !! Could not determine repository location'
|
||||
)
|
||||
req = dist.as_requirement()
|
||||
editable = False
|
||||
else:
|
||||
editable = False
|
||||
def from_dist(cls, dist):
|
||||
req, editable, comments = get_requirement_info(dist)
|
||||
if req is None:
|
||||
req = dist.as_requirement()
|
||||
specs = req.specs
|
||||
assert len(specs) == 1 and specs[0][0] in ["==", "==="], \
|
||||
'Expected 1 spec with == or ===; specs = %r; dist = %r' % \
|
||||
(specs, dist)
|
||||
version = specs[0][1]
|
||||
ver_match = cls._rev_re.search(version)
|
||||
date_match = cls._date_re.search(version)
|
||||
if ver_match or date_match:
|
||||
svn_backend = vcs.get_backend('svn')
|
||||
if svn_backend:
|
||||
svn_location = svn_backend().get_location(
|
||||
dist,
|
||||
dependency_links,
|
||||
)
|
||||
if not svn_location:
|
||||
logger.warning(
|
||||
'Warning: cannot find svn location for %s', req,
|
||||
)
|
||||
comments.append(
|
||||
'## FIXME: could not find svn URL in dependency_links '
|
||||
'for this package:'
|
||||
)
|
||||
else:
|
||||
deprecated(
|
||||
"SVN editable detection based on dependency links "
|
||||
"will be dropped in the future.",
|
||||
replacement=None,
|
||||
gone_in="18.2",
|
||||
issue=4187,
|
||||
)
|
||||
comments.append(
|
||||
'# Installing as editable to satisfy requirement %s:' %
|
||||
req
|
||||
)
|
||||
if ver_match:
|
||||
rev = ver_match.group(1)
|
||||
else:
|
||||
rev = '{%s}' % date_match.group(1)
|
||||
editable = True
|
||||
egg_name = cls.egg_name(dist)
|
||||
req = make_vcs_requirement_url(svn_location, rev, egg_name)
|
||||
|
||||
return (req, editable, comments)
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist, dependency_links):
|
||||
args = cls._init_args_from_dist(dist, dependency_links)
|
||||
return cls(dist.project_name, *args)
|
||||
|
||||
@staticmethod
|
||||
def egg_name(dist):
|
||||
name = dist.egg_name()
|
||||
match = re.search(r'-py\d\.\d$', name)
|
||||
if match:
|
||||
name = name[:match.start()]
|
||||
return name
|
||||
return cls(dist.project_name, req, editable, comments=comments)
|
||||
|
||||
def __str__(self):
|
||||
req = self.req
|
||||
|
|
|
@ -11,7 +11,6 @@ InstallRequirement.
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
|
@ -258,7 +257,7 @@ def install_req_from_line(
|
|||
elif '=' in req and not any(op in req for op in operators):
|
||||
add_msg = "= is not a valid operator. Did you mean == ?"
|
||||
else:
|
||||
add_msg = traceback.format_exc()
|
||||
add_msg = ""
|
||||
raise InstallationError(
|
||||
"Invalid requirement: '%s'\n%s" % (req, add_msg)
|
||||
)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue