Merge branch 'master' into cache/ephem-wheel-cache

This commit is contained in:
Pradyun Gedam 2017-11-17 12:32:40 +05:30 committed by GitHub
commit 01d97e71f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 655 additions and 227 deletions

View File

@ -1,4 +1,4 @@
[run]
branch = True
omit =
pip/_vendor/*
src/pip/_vendor/*

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ docs/build/
# Unit test / coverage reports
.tox/
htmlcov/
.coverage
.coverage.*
.cache

View File

@ -3,16 +3,19 @@ include LICENSE.txt
include NEWS.rst
include README.rst
include pyproject.toml
include src/pip/_vendor/README.rst
include src/pip/_vendor/vendor.txt
include docs/docutils.conf
exclude .coveragerc
exclude .mailmap
exclude .travis.yml
exclude .landscape.yml
exclude src/pip/_vendor/Makefile
exclude tox.ini
exclude dev-requirements.txt
exclude *-requirements.txt
exclude appveyor.yml
recursive-include src/pip/_vendor *.pem

View File

@ -769,7 +769,7 @@
than erroring out. (#963)
- ``pip bundle`` and support for installing from pybundle files is now
considered deprecated and will be removed in pip v1.5.
- Fix a number of isses related to cleaning up and not reusing build
- Fix a number of issues related to cleaning up and not reusing build
directories. (#413, #709, #634, #602, #939, #865, #948)
- Added a User Agent so that pip is identifiable in logs. (#901)
- Added ssl and --user support to get-pip.py. Thanks Gabriel de Perthuis.

View File

@ -1,9 +1,7 @@
pip
===
The `PyPA recommended
<https://packaging.python.org/en/latest/current/>`_
tool for installing Python packages.
The `PyPA recommended`_ tool for installing Python packages.
.. image:: https://img.shields.io/pypi/v/pip.svg
:target: https://pypi.python.org/pypi/pip
@ -14,24 +12,31 @@ tool for installing Python packages.
.. image:: https://img.shields.io/appveyor/ci/pypa/pip.svg
:target: https://ci.appveyor.com/project/pypa/pip/history
.. image:: https://readthedocs.org/projects/pip/badge/?version=stable
:target: https://pip.pypa.io/en/stable
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
:target: https://pip.pypa.io/en/latest
* `Installation <https://pip.pypa.io/en/stable/installing.html>`_
* `Documentation <https://pip.pypa.io/>`_
* `Changelog <https://pip.pypa.io/en/stable/news.html>`_
* `GitHub Page <https://github.com/pypa/pip>`_
* `Issue Tracking <https://github.com/pypa/pip/issues>`_
* `User mailing list <http://groups.google.com/group/python-virtualenv>`_
* `Dev mailing list <http://groups.google.com/group/pypa-dev>`_
* `Installation`_
* `Documentation`_
* `Changelog`_
* `GitHub Page`_
* `Issue Tracking`_
* `User mailing list`_
* `Dev mailing list`_
* User IRC: #pypa on Freenode.
* Dev IRC: #pypa-dev on Freenode.
Code of Conduct
---------------
Everyone interacting in the pip project's codebases, issue trackers, chat
rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_.
rooms and mailing lists is expected to follow the `PyPA Code of Conduct`_.
.. _PyPA recommended: https://packaging.python.org/en/latest/current/
.. _Installation: https://pip.pypa.io/en/stable/installing.html
.. _Documentation: https://pip.pypa.io/en/stable/
.. _Changelog: https://pip.pypa.io/en/stable/news.html
.. _GitHub Page: https://github.com/pypa/pip
.. _Issue Tracking: https://github.com/pypa/pip/issues
.. _User mailing list: http://groups.google.com/group/python-virtualenv
.. _Dev mailing list: http://groups.google.com/group/pypa-dev
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/

View File

@ -2,6 +2,7 @@ freezegun
pretend
pytest
pytest-catchlog
pytest-cov
pytest-rerunfailures
pytest-timeout
pytest-xdist

7
docs-requirements.txt Normal file
View File

@ -0,0 +1,7 @@
sphinx == 1.6.*
git+https://github.com/python/python-docs-theme.git#egg=python-docs-theme
git+https://github.com/pypa/pypa-docs-theme.git#egg=pypa-docs-theme
# XXX: This is a workaround for conf.py not seeing the development pip version
# when the documentation is built on ReadTheDocs.
.

View File

@ -68,6 +68,11 @@ try:
except ImportError:
version = release = 'dev'
# We have this here because readthedocs plays tricks sometimes and there seems
# to be a hiesenbug, related to the version of pip discovered. This is here to
# help debug that if someone decides to do that in the future.
print(version)
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
@ -122,7 +127,7 @@ html_theme = "pypa_theme"
html_theme_options = {
'collapsiblesidebar': True,
'externalrefs': True,
'navigation_depth': 2,
'navigation_depth': 3,
'issues_url': 'https://github.com/pypa/pip/issues'
}

2
docs/docutils.conf Normal file
View File

@ -0,0 +1,2 @@
[restructuredtext parser]
smart_quotes = no

View File

@ -2,13 +2,14 @@
import optparse
import sys
from textwrap import dedent
from docutils import nodes
from docutils.parsers import rst
from docutils.statemachine import ViewList
from textwrap import dedent
from pip._internal import cmdoptions
from pip._internal.commands import commands_dict as commands
from pip._internal.utils.misc import get_prog
class PipCommandUsage(rst.Directive):
@ -16,8 +17,7 @@ class PipCommandUsage(rst.Directive):
def run(self):
cmd = commands[self.arguments[0]]
prog = '%s %s' % (get_prog(), cmd.name)
usage = dedent(cmd.usage.replace('%prog', prog)).strip()
usage = dedent(cmd.usage.replace('%prog', 'pip')).strip()
node = nodes.literal_block(usage, usage)
return [node]

View File

@ -8,8 +8,8 @@ Install a package from `PyPI`_:
::
$ pip install SomePackage
[...]
Successfully installed SomePackage
[...]
Successfully installed SomePackage
Install a package already downloaded from `PyPI`_ or got elsewhere.
This is useful if the target machine does not have a network connection:
@ -17,49 +17,49 @@ This is useful if the target machine does not have a network connection:
::
$ pip install SomePackage-1.0-py2.py3-none-any.whl
[...]
Successfully installed SomePackage
[...]
Successfully installed SomePackage
Show what files were installed:
::
$ pip show --files SomePackage
Name: SomePackage
Version: 1.0
Location: /my/env/lib/pythonx.x/site-packages
Files:
../somepackage/__init__.py
[...]
Name: SomePackage
Version: 1.0
Location: /my/env/lib/pythonx.x/site-packages
Files:
../somepackage/__init__.py
[...]
List what packages are outdated:
::
$ pip list --outdated
SomePackage (Current: 1.0 Latest: 2.0)
SomePackage (Current: 1.0 Latest: 2.0)
Upgrade a package:
::
$ pip install --upgrade SomePackage
[...]
Found existing installation: SomePackage 1.0
Uninstalling SomePackage:
Successfully uninstalled SomePackage
Running setup.py install for SomePackage
Successfully installed SomePackage
[...]
Found existing installation: SomePackage 1.0
Uninstalling SomePackage:
Successfully uninstalled SomePackage
Running setup.py install for SomePackage
Successfully installed SomePackage
Uninstall a package:
::
$ pip uninstall SomePackage
Uninstalling SomePackage:
/my/env/lib/pythonx.x/site-packages/somepackage
Proceed (y/n)? y
Successfully uninstalled SomePackage
Uninstalling SomePackage:
/my/env/lib/pythonx.x/site-packages/somepackage
Proceed (y/n)? y
Successfully uninstalled SomePackage
.. _PyPI: http://pypi.python.org/pypi/

View File

@ -3,7 +3,7 @@ Reference Guide
===============
.. toctree::
:maxdepth: 1
:maxdepth: 2
pip
pip_install

View File

@ -1,6 +1,8 @@
pip
---
.. contents::
Usage
*****
@ -22,7 +24,9 @@ Console logging
~~~~~~~~~~~~~~~
pip offers :ref:`-v, --verbose <--verbose>` and :ref:`-q, --quiet <--quiet>`
to control the console log level.
to control the console log level. By default, some messages (error and warnings)
are colored in the terminal. If you want to suppress the colored output use
:ref:`--no-color <--no-color>`.
.. _`FileLogging`:

View File

@ -3,6 +3,8 @@
pip check
---------
.. contents::
Usage
*****

View File

@ -4,6 +4,8 @@
pip config
------------
.. contents::
Usage
*****

View File

@ -4,6 +4,8 @@
pip download
------------
.. contents::
Usage
*****

View File

@ -4,6 +4,8 @@
pip freeze
-----------
.. contents::
Usage
*****

View File

@ -3,6 +3,8 @@
pip hash
------------
.. contents::
Usage
*****

View File

@ -3,6 +3,8 @@
pip install
-----------
.. contents::
Usage
*****

View File

@ -3,6 +3,8 @@
pip list
---------
.. contents::
Usage
*****

View File

@ -3,6 +3,8 @@
pip search
----------
.. contents::
Usage
*****

View File

@ -3,6 +3,9 @@
pip show
--------
.. contents::
Usage
*****

View File

@ -3,6 +3,8 @@
pip uninstall
-------------
.. contents::
Usage
*****

View File

@ -4,6 +4,8 @@
pip wheel
---------
.. contents::
Usage
*****

View File

@ -2,6 +2,8 @@
User Guide
==========
.. contents::
Running pip
***********
@ -728,7 +730,7 @@ change without notice. While we do try not to break things as much as possible,
the internal APIs can change at any time, for any reason. It also means that we
generally *won't* fix issues that are a result of using pip in an unsupported way.
It should also be noted that modifying the contents of ``sys.path`` in a running Python
It should also be noted that installing packages into ``sys.path`` in a running Python
process is something that should only be done with care. The import system caches
certain data, and installing new packages while a program is running may not always
behave as expected. In practice, there is rarely an issue, but it is something to be
@ -756,7 +758,5 @@ of ability. Some examples that you could consider include:
* ``setuptools`` (specifically ``pkg_resources``) - Functions for querying what
packages the user has installed on their system.
* ``wheel`` - Code for manipulating (creating, querying and installing) wheels.
* ``distlib`` - Packaging and distribution utilities (including functions for
interacting with PyPI).

2
news/2449.feature Normal file
View File

@ -0,0 +1,2 @@
Add `--no-color` to `pip`. All colored output is disabled
if this flag is detected.

1
news/3016.feature Normal file
View File

@ -0,0 +1 @@
pip uninstall now ignores the absence of a requirement and prints a warning.

1
news/3741.bugfix Normal file
View File

@ -0,0 +1 @@
Fix ``pip uninstall`` when ``easy-install.pth`` lacks a trailing newline.

5
news/4293.bugfix Normal file
View File

@ -0,0 +1,5 @@
Fix for an incorrect ``freeze`` warning message due to a package being
included in multiple requirements files that were passed to ``freeze``.
Instead of warning incorrectly that the package is not installed, pip
now warns that the package was declared multiple times and lists the
name of each requirements file that contains the package in question.

1
news/4642.feature Normal file
View File

@ -0,0 +1 @@
pip uninstall now ignores the absence of a requirement and prints a warning.

2
news/4667.bugfix Normal file
View File

@ -0,0 +1,2 @@
pip now records installed files in a deterministic manner improving
reproducibility.

View File

@ -1 +1 @@
Upgraded distlib to 0.2.5.
Upgraded distlib to 0.2.6.

View File

@ -1 +1 @@
Upgraded pkg_resources (via setuptools) to 36.4.0.
Upgraded pkg_resources (via setuptools) to 36.6.0.

1
news/six.vendor Normal file
View File

@ -0,0 +1 @@
Upgraded six to 1.11.0.

View File

@ -132,6 +132,11 @@ class Command(object):
if options.log:
root_level = "DEBUG"
if options.no_color:
logger_class = "logging.StreamHandler"
else:
logger_class = "pip._internal.utils.logging.ColorizedStreamHandler"
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
@ -150,16 +155,14 @@ class Command(object):
"handlers": {
"console": {
"level": level,
"class":
"pip._internal.utils.logging.ColorizedStreamHandler",
"class": logger_class,
"stream": self.log_streams[0],
"filters": ["exclude_warnings"],
"formatter": "indent",
},
"console_errors": {
"level": "WARNING",
"class":
"pip._internal.utils.logging.ColorizedStreamHandler",
"class": logger_class,
"stream": self.log_streams[1],
"formatter": "indent",
},

View File

@ -102,6 +102,15 @@ verbose = partial(
help='Give more output. Option is additive, and can be used up to 3 times.'
)
no_color = partial(
Option,
'--no-color',
dest='no_color',
action='store_true',
default=False,
help="Suppress colored output",
)
version = partial(
Option,
'-V', '--version',
@ -566,6 +575,7 @@ general_group = {
cache_dir,
no_cache,
disable_pip_version_check,
no_color,
]
}

View File

@ -277,22 +277,20 @@ class InstallCommand(RequirementCommand):
)
resolver.resolve(requirement_set)
# on -d don't do complex things like building
# wheels, and don't try to build wheels when wheel is
# not installed.
# If caching is disabled or wheel is not installed don't
# try to build wheels.
if wheel and options.cache_dir:
# build wheels before install.
wb = WheelBuilder(
requirement_set,
finder,
preparer,
wheel_cache,
build_options=[],
global_options=[],
finder, preparer, wheel_cache,
build_options=[], global_options=[],
)
# Ignore the result: a failed wheel will be
# installed from the sdist/vcs whatever.
wb.build(session=session, autobuilding=True)
wb.build(
requirement_set.requirements.values(),
session=session, autobuilding=True
)
installed = requirement_set.install(
install_options,

View File

@ -64,7 +64,8 @@ class UninstallCommand(Command):
'"pip help %(name)s")' % dict(name=self.name)
)
for req in reqs_to_uninstall.values():
req.uninstall(
uninstall_pathset = req.uninstall(
auto_confirm=options.yes, verbose=options.verbose != 0
)
req.uninstalled_pathset.commit()
if uninstall_pathset:
uninstall_pathset.commit()

View File

@ -177,15 +177,14 @@ class WheelCommand(RequirementCommand):
# build wheels
wb = WheelBuilder(
requirement_set,
finder,
preparer,
wheel_cache,
finder, preparer, wheel_cache,
build_options=options.build_options or [],
global_options=options.global_options or [],
no_clean=options.no_clean,
)
wheels_built_successfully = wb.build(session=session)
wheels_built_successfully = wb.build(
requirement_set.requirements.values(), session=session,
)
if not wheels_built_successfully:
raise CommandError(
"Failed to build one or more wheels"

View File

@ -1,11 +1,12 @@
from __future__ import absolute_import
import collections
import logging
import os
import re
import warnings
from pip._vendor import pkg_resources
from pip._vendor import pkg_resources, six
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.pkg_resources import RequirementParseError
@ -70,6 +71,9 @@ def freeze(
# requirements files, so we need to keep track of what has been emitted
# so that we don't emit it again if it's seen again
emitted_options = set()
# keep track of which files a requirement is in so that we can
# give an accurate warning if a requirement appears multiple times.
req_files = collections.defaultdict(list)
for req_file_path in requirement:
with open(req_file_path) as req_file:
for line in req_file:
@ -119,14 +123,28 @@ def freeze(
" this warning)"
)
elif line_req.name not in installations:
logger.warning(
"Requirement file [%s] contains %s, but that "
"package is not installed",
req_file_path, COMMENT_RE.sub('', line).strip(),
)
# either it's not installed, or it is installed
# 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",
req_file_path,
COMMENT_RE.sub('', line).strip(),
)
else:
req_files[line_req.name].append(req_file_path)
else:
yield str(installations[line_req.name]).rstrip()
del installations[line_req.name]
req_files[line_req.name].append(req_file_path)
# Warn about requirements that were included multiple times (in a
# single requirements file or in different requirements files).
for name, files in six.iteritems(req_files):
if len(files) > 1:
logger.warning("Requirement %s included multiple times [%s]",
name, ', '.join(sorted(set(files))))
yield(
'## The following requirements were added by '

View File

@ -648,13 +648,13 @@ class InstallRequirement(object):
"""
if not self.check_if_exists():
raise UninstallationError(
"Cannot uninstall requirement %s, not installed" % (self.name,)
)
logger.warning("Skipping %s as it is not installed.", self.name)
return
dist = self.satisfied_by or self.conflicts_with
self.uninstalled_pathset = UninstallPathSet.from_dist(dist)
self.uninstalled_pathset.remove(auto_confirm, verbose)
uninstalled_pathset = UninstallPathSet.from_dist(dist)
uninstalled_pathset.remove(auto_confirm, verbose)
return uninstalled_pathset
def archive(self, build_dir):
assert self.source_dir
@ -802,6 +802,7 @@ class InstallRequirement(object):
new_lines.append(
os.path.relpath(prepend_root(filename), egg_info_dir)
)
new_lines.sort()
ensure_dir(egg_info_dir)
inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
with open(inst_files_path, 'w') as f:

View File

@ -216,7 +216,9 @@ class RequirementSet(object):
requirement.conflicts_with,
)
with indent_log():
requirement.uninstall(auto_confirm=True)
uninstalled_pathset = requirement.uninstall(
auto_confirm=True
)
try:
requirement.install(
install_options,
@ -231,7 +233,7 @@ class RequirementSet(object):
)
# if install did not succeed, rollback previous uninstall
if should_rollback:
requirement.uninstalled_pathset.rollback()
uninstalled_pathset.rollback()
raise
else:
should_commit = (
@ -239,7 +241,7 @@ class RequirementSet(object):
requirement.install_succeeded
)
if should_commit:
requirement.uninstalled_pathset.commit()
uninstalled_pathset.commit()
requirement.remove_temporary_source()
return to_install

View File

@ -430,6 +430,9 @@ class UninstallPthEntries(object):
endline = '\r\n'
else:
endline = '\n'
# handle missing trailing newline
if lines and not lines[-1].endswith(endline.encode("utf-8")):
lines[-1] = lines[-1] + endline.encode("utf-8")
for entry in self.entries:
try:
logger.debug('Removing entry: %s', entry)

View File

@ -657,9 +657,8 @@ class BuildEnvironment(object):
class WheelBuilder(object):
"""Build wheels from a RequirementSet."""
def __init__(self, requirement_set, finder, preparer, wheel_cache,
def __init__(self, finder, preparer, wheel_cache,
build_options=None, global_options=None, no_clean=False):
self.requirement_set = requirement_set
self.finder = finder
self.preparer = preparer
self.wheel_cache = wheel_cache
@ -791,7 +790,7 @@ class WheelBuilder(object):
logger.error('Failed cleaning build dir for %s', req.name)
return False
def build(self, session, autobuilding=False):
def build(self, requirements, session, autobuilding=False):
"""Build wheels.
:param unpack: If True, replace the sdist we built from with the
@ -805,10 +804,8 @@ class WheelBuilder(object):
)
assert building_is_possible
reqset = self.requirement_set.requirements.values()
buildset = []
for req in reqset:
for req in requirements:
ephem_cache = False
if req.constraint:
continue

View File

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2016 Vinay Sajip.
# Copyright (C) 2012-2017 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
import logging
__version__ = '0.2.5'
__version__ = '0.2.6'
class DistlibException(Exception):
pass

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2016 Vinay Sajip.
# Copyright (C) 2013-2017 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
@ -12,7 +12,7 @@ import sys
try:
import ssl
except ImportError:
except ImportError: # pragma: no cover
ssl = None
if sys.version_info[0] < 3: # pragma: no cover
@ -272,7 +272,7 @@ from zipfile import ZipFile as BaseZipFile
if hasattr(BaseZipFile, '__enter__'): # pragma: no cover
ZipFile = BaseZipFile
else:
else: # pragma: no cover
from zipfile import ZipExtFile as BaseZipExtFile
class ZipExtFile(BaseZipExtFile):
@ -329,7 +329,13 @@ try:
fsencode = os.fsencode
fsdecode = os.fsdecode
except AttributeError: # pragma: no cover
_fsencoding = sys.getfilesystemencoding()
# Issue #99: on some systems (e.g. containerised),
# sys.getfilesystemencoding() returns None, and we need a real value,
# so fall back to utf-8. From the CPython 2.7 docs relating to Unix and
# sys.getfilesystemencoding(): the return value is "the users preference
# according to the result of nl_langinfo(CODESET), or None if the
# nl_langinfo(CODESET) failed."
_fsencoding = sys.getfilesystemencoding() or 'utf-8'
if _fsencoding == 'mbcs':
_fserrors = 'strict'
else:

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2016 The Python Software Foundation.
# Copyright (C) 2012-2017 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""PEP 376 implementation."""
@ -265,18 +265,23 @@ class DistributionPath(object):
(name, version))
for dist in self.get_distributions():
provided = dist.provides
# We hit a problem on Travis where enum34 was installed and doesn't
# have a provides attribute ...
if not hasattr(dist, 'provides'):
logger.debug('No "provides": %s', dist)
else:
provided = dist.provides
for p in provided:
p_name, p_ver = parse_name_and_version(p)
if matcher is None:
if p_name == name:
yield dist
break
else:
if p_name == name and matcher.match(p_ver):
yield dist
break
for p in provided:
p_name, p_ver = parse_name_and_version(p)
if matcher is None:
if p_name == name:
yield dist
break
else:
if p_name == name and matcher.match(p_ver):
yield dist
break
def get_file_path(self, name, relative_path):
"""
@ -1025,20 +1030,21 @@ class EggInfoDistribution(BaseInstalledDistribution):
:returns: iterator of paths
"""
record_path = os.path.join(self.path, 'installed-files.txt')
skip = True
with codecs.open(record_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line == './':
skip = False
continue
if not skip:
p = os.path.normpath(os.path.join(self.path, line))
if p.startswith(self.path):
if absolute:
yield p
else:
yield line
if os.path.exists(record_path):
skip = True
with codecs.open(record_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line == './':
skip = False
continue
if not skip:
p = os.path.normpath(os.path.join(self.path, line))
if p.startswith(self.path):
if absolute:
yield p
else:
yield line
def __eq__(self, other):
return (isinstance(other, EggInfoDistribution) and

View File

@ -24,7 +24,7 @@ from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url,
HTTPRedirectHandler as BaseRedirectHandler, text_type,
Request, HTTPError, URLError)
from .database import Distribution, DistributionPath, make_dist
from .metadata import Metadata
from .metadata import Metadata, MetadataInvalidError
from .util import (cached_property, parse_credentials, ensure_slash,
split_filename, get_project_data, parse_requirement,
parse_name_and_version, ServerProxy, normalize_name)
@ -69,7 +69,7 @@ class RedirectHandler(BaseRedirectHandler):
if key in headers:
newurl = headers[key]
break
if newurl is None:
if newurl is None: # pragma: no cover
return
urlparts = urlparse(newurl)
if urlparts.scheme == '':
@ -175,7 +175,7 @@ class Locator(object):
This calls _get_project to do all the work, and just implements a caching layer on top.
"""
if self._cache is None:
if self._cache is None: # pragma: no cover
result = self._get_project(name)
elif name in self._cache:
result = self._cache[name]
@ -241,7 +241,7 @@ class Locator(object):
result = None
scheme, netloc, path, params, query, frag = urlparse(url)
if frag.lower().startswith('egg='):
if frag.lower().startswith('egg='): # pragma: no cover
logger.debug('%s: version hint in fragment: %r',
project_name, frag)
m = HASHER_HASH.match(frag)
@ -250,7 +250,7 @@ class Locator(object):
else:
algo, digest = None, None
origpath = path
if path and path[-1] == '/':
if path and path[-1] == '/': # pragma: no cover
path = path[:-1]
if path.endswith('.whl'):
try:
@ -272,13 +272,15 @@ class Locator(object):
}
except Exception as e: # pragma: no cover
logger.warning('invalid path for wheel: %s', path)
elif path.endswith(self.downloadable_extensions):
elif not path.endswith(self.downloadable_extensions): # pragma: no cover
logger.debug('Not downloadable: %s', path)
else: # downloadable extension
path = filename = posixpath.basename(path)
for ext in self.downloadable_extensions:
if path.endswith(ext):
path = path[:-len(ext)]
t = self.split_filename(path, project_name)
if not t:
if not t: # pragma: no cover
logger.debug('No match for project/version: %s', path)
else:
name, version, pyver = t
@ -291,7 +293,7 @@ class Locator(object):
params, query, '')),
#'packagetype': 'sdist',
}
if pyver:
if pyver: # pragma: no cover
result['python-version'] = pyver
break
if result and algo:
@ -352,7 +354,7 @@ class Locator(object):
"""
result = None
r = parse_requirement(requirement)
if r is None:
if r is None: # pragma: no cover
raise DistlibException('Not a valid requirement: %r' % requirement)
scheme = get_scheme(self.scheme)
self.matcher = matcher = scheme.matcher(r.requirement)
@ -390,7 +392,7 @@ class Locator(object):
d = {}
sd = versions.get('digests', {})
for url in result.download_urls:
if url in sd:
if url in sd: # pragma: no cover
d[url] = sd[url]
result.digests = d
self.matcher = None
@ -730,11 +732,14 @@ class SimpleScrapingLocator(Locator):
continue
for link, rel in page.links:
if link not in self._seen:
self._seen.add(link)
if (not self._process_download(link) and
self._should_queue(link, url, rel)):
logger.debug('Queueing %s from %s', link, url)
self._to_fetch.put(link)
try:
self._seen.add(link)
if (not self._process_download(link) and
self._should_queue(link, url, rel)):
logger.debug('Queueing %s from %s', link, url)
self._to_fetch.put(link)
except MetadataInvalidError: # e.g. invalid versions
pass
except Exception as e: # pragma: no cover
self.errors.put(text_type(e))
finally:

View File

@ -119,9 +119,12 @@ def interpret(marker, execution_context=None):
:param execution_context: The context used for name lookup.
:type execution_context: mapping
"""
expr, rest = parse_marker(marker)
try:
expr, rest = parse_marker(marker)
except Exception as e:
raise SyntaxError('Unable to interpret marker syntax: %s: %s' % (marker, e))
if rest and rest[0] != '#':
raise SyntaxError('unexpected trailing data: %s' % rest)
raise SyntaxError('unexpected trailing data in marker: %s: %s' % (marker, rest))
context = dict(DEFAULT_CONTEXT)
if execution_context:
context.update(execution_context)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2016 Vinay Sajip.
# Copyright (C) 2013-2017 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#

View File

@ -136,6 +136,37 @@ class ScriptMaker(object):
return executable
return '/usr/bin/env %s' % executable
def _build_shebang(self, executable, post_interp):
"""
Build a shebang line. In the simple case (on Windows, or a shebang line
which is not too long or contains spaces) use a simple formulation for
the shebang. Otherwise, use /bin/sh as the executable, with a contrived
shebang which allows the script to run either under Python or sh, using
suitable quoting. Thanks to Harald Nordgren for his input.
See also: http://www.in-ulm.de/~mascheck/various/shebang/#length
https://hg.mozilla.org/mozilla-central/file/tip/mach
"""
if os.name != 'posix':
simple_shebang = True
else:
# Add 3 for '#!' prefix and newline suffix.
shebang_length = len(executable) + len(post_interp) + 3
if sys.platform == 'darwin':
max_shebang_length = 512
else:
max_shebang_length = 127
simple_shebang = ((b' ' not in executable) and
(shebang_length <= max_shebang_length))
if simple_shebang:
result = b'#!' + executable + post_interp + b'\n'
else:
result = b'#!/bin/sh\n'
result += b"'''exec' " + executable + post_interp + b' "$0" "$@"\n'
result += b"' '''"
return result
def _get_shebang(self, encoding, post_interp=b'', options=None):
enquote = True
if self.executable:
@ -169,7 +200,7 @@ class ScriptMaker(object):
if (sys.platform == 'cli' and '-X:Frames' not in post_interp
and '-X:FullFrames' not in post_interp): # pragma: no cover
post_interp += b' -X:Frames'
shebang = b'#!' + executable + post_interp + b'\n'
shebang = self._build_shebang(executable, post_interp)
# Python parser starts to read a script using UTF-8 until
# it gets a #coding:xxx cookie. The shebang has to be the
# first line of a file, the #coding:xxx cookie cannot be

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2012-2016 The Python Software Foundation.
# Copyright (C) 2012-2017 The Python Software Foundation.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
"""

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013-2016 Vinay Sajip.
# Copyright (C) 2013-2017 Vinay Sajip.
# Licensed to the Python Software Foundation under a contributor agreement.
# See LICENSE.txt and CONTRIBUTORS.txt.
#
@ -35,11 +35,11 @@ logger = logging.getLogger(__name__)
cache = None # created when needed
if hasattr(sys, 'pypy_version_info'):
if hasattr(sys, 'pypy_version_info'): # pragma: no cover
IMP_PREFIX = 'pp'
elif sys.platform.startswith('java'):
elif sys.platform.startswith('java'): # pragma: no cover
IMP_PREFIX = 'jy'
elif sys.platform == 'cli':
elif sys.platform == 'cli': # pragma: no cover
IMP_PREFIX = 'ip'
else:
IMP_PREFIX = 'cp'

View File

@ -2028,51 +2028,121 @@ def find_on_path(importer, path_item, only=False):
path_item, os.path.join(path_item, 'EGG-INFO')
)
)
else:
try:
entries = os.listdir(path_item)
except (PermissionError, NotADirectoryError):
return
except OSError as e:
# Ignore the directory if does not exist, not a directory or we
# don't have permissions
if (e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
# Python 2 on Windows needs to be handled this way :(
or hasattr(e, "winerror") and e.winerror == 267):
return
return
entries = safe_listdir(path_item)
# for performance, before sorting by version,
# screen entries for only those that will yield
# distributions
filtered = (
entry
for entry in entries
if dist_factory(path_item, entry, only)
)
# scan for .egg and .egg-info in directory
path_item_entries = _by_version_descending(filtered)
for entry in path_item_entries:
fullpath = os.path.join(path_item, entry)
factory = dist_factory(path_item, entry, only)
for dist in factory(fullpath):
yield dist
def dist_factory(path_item, entry, only):
"""
Return a dist_factory for a path_item and entry
"""
lower = entry.lower()
is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info')))
return (
distributions_from_metadata
if is_meta else
find_distributions
if not only and _is_egg_path(entry) else
resolve_egg_link
if not only and lower.endswith('.egg-link') else
NoDists()
)
class NoDists:
"""
>>> bool(NoDists())
False
>>> list(NoDists()('anything'))
[]
"""
def __bool__(self):
return False
if six.PY2:
__nonzero__ = __bool__
def __call__(self, fullpath):
return iter(())
def safe_listdir(path):
"""
Attempt to list contents of path, but suppress some exceptions.
"""
try:
return os.listdir(path)
except (PermissionError, NotADirectoryError):
pass
except OSError as e:
# Ignore the directory if does not exist, not a directory or
# permission denied
ignorable = (
e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT)
# Python 2 on Windows needs to be handled this way :(
or getattr(e, "winerror", None) == 267
)
if not ignorable:
raise
# scan for .egg and .egg-info in directory
path_item_entries = _by_version_descending(entries)
for entry in path_item_entries:
lower = entry.lower()
if lower.endswith('.egg-info') or lower.endswith('.dist-info'):
fullpath = os.path.join(path_item, entry)
if os.path.isdir(fullpath):
# egg-info directory, allow getting metadata
if len(os.listdir(fullpath)) == 0:
# Empty egg directory, skip.
continue
metadata = PathMetadata(path_item, fullpath)
else:
metadata = FileMetadata(fullpath)
yield Distribution.from_location(
path_item, entry, metadata, precedence=DEVELOP_DIST
)
elif not only and _is_egg_path(entry):
dists = find_distributions(os.path.join(path_item, entry))
for dist in dists:
yield dist
elif not only and lower.endswith('.egg-link'):
with open(os.path.join(path_item, entry)) as entry_file:
entry_lines = entry_file.readlines()
for line in entry_lines:
if not line.strip():
continue
path = os.path.join(path_item, line.rstrip())
dists = find_distributions(path)
for item in dists:
yield item
break
return ()
def distributions_from_metadata(path):
root = os.path.dirname(path)
if os.path.isdir(path):
if len(os.listdir(path)) == 0:
# empty metadata dir; skip
return
metadata = PathMetadata(root, path)
else:
metadata = FileMetadata(path)
entry = os.path.basename(path)
yield Distribution.from_location(
root, entry, metadata, precedence=DEVELOP_DIST,
)
def non_empty_lines(path):
"""
Yield non-empty lines from file at path
"""
with open(path) as f:
for line in f:
line = line.strip()
if line:
yield line
def resolve_egg_link(path):
"""
Given a path to an .egg-link, resolve distributions
present in the referenced path.
"""
referenced_paths = non_empty_lines(path)
resolved_paths = (
os.path.join(os.path.dirname(path), ref)
for ref in referenced_paths
)
dist_groups = map(find_distributions, resolved_paths)
return next(dist_groups, ())
register_finder(pkgutil.ImpImporter, find_on_path)
@ -2250,9 +2320,7 @@ def _is_egg_path(path):
"""
Determine if given path appears to be an egg.
"""
return (
path.lower().endswith('.egg')
)
return path.lower().endswith('.egg')
def _is_unpacked_egg(path):

View File

@ -1,6 +1,4 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2015 Benjamin Peterson
# Copyright (c) 2010-2017 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -20,6 +18,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Utilities for writing code that runs on Python 2 and 3"""
from __future__ import absolute_import
import functools
@ -29,7 +29,7 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.10.0"
__version__ = "1.11.0"
# Useful for very coarse version differentiation.
@ -241,6 +241,7 @@ _moved_attributes = [
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
MovedAttribute("getoutput", "commands", "subprocess"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
@ -262,10 +263,11 @@ _moved_attributes = [
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
@ -337,10 +339,12 @@ _urllib_parse_moved_attributes = [
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
@ -416,6 +420,8 @@ _urllib_request_moved_attributes = [
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
@ -679,11 +685,15 @@ if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
finally:
value = None
tb = None
else:
def exec_(_code_, _globs_=None, _locs_=None):
@ -699,19 +709,28 @@ else:
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
try:
raise tp, value, tb
finally:
tb = None
""")
if sys.version_info[:2] == (3, 2):
exec_("""def raise_from(value, from_value):
if from_value is None:
raise value
raise value from from_value
try:
if from_value is None:
raise value
raise value from from_value
finally:
value = None
""")
elif sys.version_info[:2] > (3, 2):
exec_("""def raise_from(value, from_value):
raise value from from_value
try:
raise value from from_value
finally:
value = None
""")
else:
def raise_from(value, from_value):
@ -802,10 +821,14 @@ def with_metaclass(meta, *bases):
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
class metaclass(type):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
@classmethod
def __prepare__(cls, name, this_bases):
return meta.__prepare__(name, bases)
return type.__new__(metaclass, 'temporary_class', (), {})

View File

@ -1,8 +1,8 @@
appdirs==1.4.3
distlib==0.2.5
distlib==0.2.6
distro==1.0.4
html5lib==1.0b10
six==1.10.0
six==1.11.0
colorama==0.3.9
CacheControl==0.12.3
msgpack-python==0.4.8
@ -18,5 +18,5 @@ requests==2.18.4
idna==2.6
urllib3==1.22
certifi==2017.7.27.1
setuptools==36.4.0
setuptools==36.6.0
webencodings==0.5.1

View File

@ -498,6 +498,78 @@ def test_freeze_with_requirement_option_multiple(script):
assert result.stdout.count("--index-url http://ignore") == 1
def test_freeze_with_requirement_option_package_repeated_one_file(script):
"""
Test freezing with single requirements file that contains a package
multiple times
"""
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
simple2
simple2
NoExist
""") + _freeze_req_opts)
result = script.pip_install_local('simple2==1.0')
result = script.pip_install_local('meta')
result = script.pip(
'freeze', '--requirement', 'hint1.txt',
expect_stderr=True,
)
expected_out = textwrap.dedent("""\
simple2==1.0
""")
expected_out += _freeze_req_opts
expected_out += "## The following requirements were added by pip freeze:"
expected_out += '\n' + textwrap.dedent("""\
...meta==1.0...
""")
_check_output(result.stdout, expected_out)
err1 = ("Requirement file [hint1.txt] contains NoExist, "
"but that package is not installed\n")
err2 = "Requirement simple2 included multiple times [hint1.txt]\n"
assert err1 in result.stderr
assert err2 in result.stderr
# there shouldn't be any other 'is not installed' warnings
assert result.stderr.count('is not installed') == 1
def test_freeze_with_requirement_option_package_repeated_multi_file(script):
"""
Test freezing with multiple requirements file that contain a package
"""
script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
simple
""") + _freeze_req_opts)
script.scratch_path.join('hint2.txt').write(textwrap.dedent("""\
simple
NoExist
""") + _freeze_req_opts)
result = script.pip_install_local('simple==1.0')
result = script.pip_install_local('meta')
result = script.pip(
'freeze', '--requirement', 'hint1.txt',
'--requirement', 'hint2.txt',
expect_stderr=True,
)
expected_out = textwrap.dedent("""\
simple==1.0
""")
expected_out += _freeze_req_opts
expected_out += "## The following requirements were added by pip freeze:"
expected_out += '\n' + textwrap.dedent("""\
...meta==1.0...
""")
_check_output(result.stdout, expected_out)
err1 = ("Requirement file [hint2.txt] contains NoExist, but that "
"package is not installed\n")
err2 = ("Requirement simple included multiple times "
"[hint1.txt, hint2.txt]\n")
assert err1 in result.stderr
assert err2 in result.stderr
# there shouldn't be any other 'is not installed' warnings
assert result.stderr.count('is not installed') == 1
@pytest.mark.network
def test_freeze_user(script, virtualenv, data):
"""

View File

@ -1258,3 +1258,25 @@ def test_installing_scripts_on_path_does_not_print_warning(script):
result = script.pip_install_local("script_wheel1")
assert "Successfully installed script-wheel1" in result.stdout, str(result)
assert "--no-warn-script-location" not in result.stderr
def test_installed_files_recorded_in_deterministic_order(script, data):
"""
Ensure that we record the files installed by a package in a deterministic
order, to make installs reproducible.
"""
to_install = data.packages.join("FSPkg")
result = script.pip('install', to_install, expect_error=False)
fspkg_folder = script.site_packages / 'fspkg'
egg_info = 'FSPkg-0.1.dev0-py%s.egg-info' % pyversion
installed_files_path = (
script.site_packages / egg_info / 'installed-files.txt'
)
assert fspkg_folder in result.files_created, str(result.stdout)
assert installed_files_path in result.files_created, str(result)
installed_files_path = result.files_created[installed_files_path].full
installed_files_lines = [
p for p in Path(installed_files_path).read_text().split('\n') if p
]
assert installed_files_lines == sorted(installed_files_lines)

View File

@ -184,7 +184,7 @@ def test_git_with_tag_name_and_update(script, tmpdir):
result = script.pip(
'install', '-e', '%s#egg=pip-test-package' %
local_checkout(
'git+http://github.com/pypa/pip-test-package.git',
'git+https://github.com/pypa/pip-test-package.git',
tmpdir.join("cache"),
),
expect_error=True,
@ -194,7 +194,7 @@ def test_git_with_tag_name_and_update(script, tmpdir):
'install', '--global-option=--version', '-e',
'%s@0.1.2#egg=pip-test-package' %
local_checkout(
'git+http://github.com/pypa/pip-test-package.git',
'git+https://github.com/pypa/pip-test-package.git',
tmpdir.join("cache"),
),
expect_error=True,
@ -211,7 +211,7 @@ def test_git_branch_should_not_be_changed(script, tmpdir):
script.pip(
'install', '-e', '%s#egg=pip-test-package' %
local_checkout(
'git+http://github.com/pypa/pip-test-package.git',
'git+https://github.com/pypa/pip-test-package.git',
tmpdir.join("cache"),
),
expect_error=True,
@ -229,7 +229,7 @@ def test_git_with_non_editable_unpacking(script, tmpdir):
result = script.pip(
'install', '--global-option=--version',
local_checkout(
'git+http://github.com/pypa/pip-test-package.git@0.1.2'
'git+https://github.com/pypa/pip-test-package.git@0.1.2'
'#egg=pip-test-package',
tmpdir.join("cache")
),

View File

@ -0,0 +1,41 @@
"""
Test specific for the --no-color option
"""
import os
import platform
import subprocess as sp
import sys
import pytest
@pytest.mark.skipif(sys.platform == 'win32',
reason="does not run on windows")
def test_no_color(script):
"""
Test uninstalling an existing package - should out put red error
We must use subprocess with the script command, since redirection
in unix platform causes text coloring to disapper. Thus, we can't
use the testing infrastructure that other options has.
"""
sp.Popen("script --flush --quiet --return /tmp/colored-output.txt"
" --command \"pip uninstall noSuchPackage\"", shell=True,
stdout=sp.PIPE, stderr=sp.PIPE).communicate()
with open("/tmp/colored-output.txt", "r") as result:
assert "\x1b" in result.read()
os.unlink("/tmp/colored-output.txt")
sp.Popen("script --flush --quiet --return /tmp/no-color-output.txt"
" --command \"pip --no-color uninstall noSuchPackage\"",
shell=True,
stdout=sp.PIPE, stderr=sp.PIPE).communicate()
with open("/tmp/no-color-output.txt", "r") as result:
assert "\x1b" not in result.read()
os.unlink("/tmp/no-color-output.txt")

View File

@ -100,6 +100,39 @@ def test_uninstall_easy_install_after_import(script):
)
@pytest.mark.network
def test_uninstall_trailing_newline(script):
"""
Uninstall behaves appropriately if easy-install.pth
lacks a trailing newline
"""
script.run('easy_install', 'INITools==0.2', expect_stderr=True)
script.run('easy_install', 'PyLogo', expect_stderr=True)
easy_install_pth = script.site_packages_path / 'easy-install.pth'
# trim trailing newline from easy-install.pth
with open(easy_install_pth) as f:
pth_before = f.read()
with open(easy_install_pth, 'w') as f:
f.write(pth_before.rstrip())
# uninstall initools
script.pip('uninstall', 'INITools', '-y')
with open(easy_install_pth) as f:
pth_after = f.read()
# verify that only initools is removed
before_without_initools = [
line for line in pth_before.splitlines()
if 'initools' not in line.lower()
]
lines_after = pth_after.splitlines()
assert lines_after == before_without_initools
@pytest.mark.network
def test_uninstall_namespace_package(script):
"""
@ -468,3 +501,25 @@ def test_uninstall_editable_and_pip_install(script, data):
) in uninstall2.files_deleted, list(uninstall2.files_deleted.keys())
list_result2 = script.pip('list', '--format=json')
assert "FSPkg" not in {p["name"] for p in json.loads(list_result2.stdout)}
def test_uninstall_ignores_missing_packages(script, data):
"""Uninstall of a non existent package prints a warning and exits cleanly
"""
result = script.pip(
'uninstall', '-y', 'non-existent-pkg', expect_stderr=True,
)
assert "Skipping non-existent-pkg as it is not installed." in result.stderr
assert result.returncode == 0, "Expected clean exit"
def test_uninstall_ignores_missing_packages_and_uninstalls_rest(script, data):
script.pip_install_local('simple')
result = script.pip(
'uninstall', '-y', 'non-existent-pkg', 'simple', expect_stderr=True,
)
assert "Skipping non-existent-pkg as it is not installed." in result.stderr
assert "Successfully uninstalled simple" in result.stdout
assert result.returncode == 0, "Expected clean exit"

View File

@ -364,10 +364,10 @@ class TestWheelBuilder(object):
with patch('pip._internal.wheel.WheelBuilder._build_one') \
as mock_build_one:
wheel_req = Mock(is_wheel=True, editable=False, constraint=False)
reqset = Mock(requirements=Mock(values=lambda: [wheel_req]),
wheel_download_dir='/wheel/dir')
wb = wheel.WheelBuilder(reqset, Mock(), Mock(), wheel_cache=None)
wb.build(Mock())
wb = wheel.WheelBuilder(
finder=Mock(), preparer=Mock(), wheel_cache=None,
)
wb.build([wheel_req], session=Mock())
assert "due to already being wheel" in caplog.text
assert mock_build_one.mock_calls == []

12
tox.ini
View File

@ -4,7 +4,7 @@ envlist =
py27, py33, py34, py35, py36, py37, pypy
[testenv]
passenv = GIT_SSL_CAINFO
passenv = CI GIT_SSL_CAINFO
setenv =
# This is required in order to get UTF-8 output inside of the subprocesses
# that our tests use.
@ -14,12 +14,12 @@ commands = py.test --timeout 300 []
install_command = python -m pip install {opts} {packages}
usedevelop = True
[testenv:docs]
deps =
sphinx == 1.6.1
git+https://github.com/python/python-docs-theme.git#egg=python-docs-theme
git+https://github.com/pypa/pypa-docs-theme.git#egg=pypa-docs-theme
[testenv:coverage-py3]
basepython = python3
commands = py.test --timeout 300 --cov=pip --cov-report=term-missing --cov-report=xml --cov-report=html tests/unit {posargs}
[testenv:docs]
deps = -r{toxinidir}/docs-requirements.txt
basepython = python2.7
commands =
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/build/html