Merge branch 'master' into master

This commit is contained in:
Pradyun Gedam 2018-11-09 17:01:47 +05:30 committed by GitHub
commit e64742d745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
179 changed files with 3810 additions and 981 deletions

View File

@ -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

1
.gitignore vendored
View File

@ -26,6 +26,7 @@ htmlcov/
nosetests.xml
coverage.xml
*.cover
tests/data/common_wheels/
# Misc
*~

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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)

View File

@ -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/

View File

@ -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/

View File

@ -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.

View File

@ -15,6 +15,7 @@ or the `pypa-dev mailing list`_, to ask questions or get involved.
getting-started
contributing
release-process
vendoring-policy
.. note::

View File

@ -0,0 +1 @@
.. include:: ../../../src/pip/_vendor/README.rst

View File

@ -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
~~~~~~~~~~~~~~~~~~~

View File

@ -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.

View File

@ -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

View File

@ -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]

View File

@ -0,0 +1 @@
Include the package name in a freeze warning if the package is not installed.

View File

@ -1 +0,0 @@
Checkout the correct branch when doing an editable Git install.

View File

@ -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.

1
news/4746.bugfix Normal file
View File

@ -0,0 +1 @@
Redact the password from the URL in various log messages.

1
news/4833.bugfix Normal file
View File

@ -0,0 +1 @@
give 401 warning if username/password do not work for URL

View File

@ -1,2 +0,0 @@
Upgrade pyparsing to 2.2.1.

1
news/5031.feature Normal file
View File

@ -0,0 +1 @@
Editable, non-VCS installs now freeze as editable.

1
news/5147.bugfix Normal file
View File

@ -0,0 +1 @@
Invalid requirement no longer causes stack trace to be printed.

1
news/5213.feature Normal file
View File

@ -0,0 +1 @@
Pip now includes license text of 3rd party libraries.

2
news/5270.bugfix Normal file
View File

@ -0,0 +1,2 @@
Handle `requests.exceptions.RetryError` raised in `PackageFinder` that was
causing pip to fail silently when some indexes were unreachable.

View File

@ -1 +0,0 @@
Allows dist options (--abi, --python-version, --platform, --implementation) when installing with --target

View File

@ -1 +0,0 @@
Support passing ``svn+ssh`` URLs with a username to ``pip install -e``.

1
news/5385.bugfix Normal file
View File

@ -0,0 +1 @@
Setting ``PIP_NO_CACHE_DIR=yes`` no longer causes pip to crash.

View File

@ -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.

2
news/5483.bugfix Normal file
View File

@ -0,0 +1,2 @@
Handle `requests.exceptions.RetryError` raised in `PackageFinder` that was
causing pip to fail silently when some indexes were unreachable.

View File

@ -1 +0,0 @@
pip now ensures that the RECORD file is sorted when installing from a wheel file.

View File

@ -1 +0,0 @@
Add support for Python 3.7.

View File

@ -1 +0,0 @@
Allow a Git ref to be installed over an existing installation.

View File

@ -1 +0,0 @@
Show a better error message when a configuration option has an invalid value.

1
news/5656.bugfix Normal file
View File

@ -0,0 +1 @@
- Improve PEP 518 build isolation: handle .pth files, so namespace packages are correctly supported under Python 3.2 and earlier.

View File

@ -1,2 +0,0 @@
Always revalidate cached simple API pages instead of blindly caching them for up to 10
minutes.

View File

@ -1 +0,0 @@
Avoid caching self-version-check information when cache is disabled.

2
news/5735.feature Normal file
View File

@ -0,0 +1,2 @@
Make ``PIP_NO_CACHE_DIR`` disable the cache also for truthy values like
``"true"``, ``"yes"``, ``"1"``, etc.

View File

@ -1 +0,0 @@
Remove the unmatched bracket in the --no-clean option's help text.

View File

@ -1 +0,0 @@
Avoid traceback printing on autocomplete after flags in the CLI.

View File

@ -1 +0,0 @@
Fix links to NEWS entry guidelines.

View File

@ -1 +0,0 @@
Fix incorrect parsing of egg names if pip needs to guess the package name.

View File

@ -1 +0,0 @@
Simplify always-true conditions in ``HTMLPage.get_page()``.

View File

@ -1 +0,0 @@
Add unit tests for egg_info_matches.

View File

@ -1 +0,0 @@
Refactor HTMLPage to reduce attributes on it.

1
news/5827.feature Normal file
View File

@ -0,0 +1 @@
A warning message is emitted when dropping an ``--[extra-]index-url`` value that points to an existing local directory.

View File

@ -1 +0,0 @@
Move static and class methods out of HTMLPage for prepare for refactoring.

1
news/5838.bugfix Normal file
View File

@ -0,0 +1 @@
Fix content type detection if a directory named like an archive is used as a package source.

1
news/5839.bugfix Normal file
View File

@ -0,0 +1 @@
Fix crashes from unparseable requirements when checking installed packages.

1
news/5841.bugfix Normal file
View File

@ -0,0 +1 @@
Fix support for invoking pip using `python src/pip ...`.

1
news/5848.bugfix Normal file
View File

@ -0,0 +1 @@
Greatly reduce memory usage when installing wheels containing large files.

2
news/5866.removal Normal file
View File

@ -0,0 +1,2 @@
Remove the deprecated SVN editable detection based on dependency links
during freeze.

1
news/5868.bugfix Normal file
View File

@ -0,0 +1 @@
Fix sorting `TypeError` in `move_wheel_files()` when installing some packages.

1
news/5870.bugfix Normal file
View File

@ -0,0 +1 @@
Canonicalize sdist file names so they can be matched to a canonicalized package name passed to ``pip install``.

1
news/5888.doc Normal file
View File

@ -0,0 +1 @@
Remove references to removed #egg=<name>-<version> functionality

1
news/5958.doc Normal file
View File

@ -0,0 +1 @@
Include the Vendoring Policy in the documentation.

2
news/5961.trivial Normal file
View File

@ -0,0 +1,2 @@
Adds hyperlinks to User IRC and Dev IRC in README.

1
news/5968.bugfix Normal file
View File

@ -0,0 +1 @@
Percent-decode special characters in SVN URL credentials.

1
news/5984.doc Normal file
View File

@ -0,0 +1 @@
Add command information in usage document for pip cmd

View File

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

1
news/deadbeef.trivial Normal file
View File

@ -0,0 +1 @@

View File

@ -1 +0,0 @@
Upgrade packaging to 18.0

View File

@ -1 +0,0 @@
Add pep517 version 0.2

View File

@ -1 +0,0 @@
Upgrade pytoml to 0.1.19

View File

@ -1 +0,0 @@
Upgrade pkg_resources to 40.4.3 (via setuptools)

View File

@ -1 +0,0 @@
Fix "Requirements Files" reference in User Guide

View File

@ -1 +1 @@
__version__ = "18.1.dev0"
__version__ = "19.0.dev0"

View File

@ -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')

View File

@ -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()

View File

@ -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.
"""

View File

@ -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]

View File

@ -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):

View File

@ -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.")

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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"""

View File

@ -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():

View File

@ -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."

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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