diff --git a/.gitignore b/.gitignore
index 0fe94900b6b..e5df8c83215 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@ build/*
pip-log.txt
pip.log
*.~
+.tox
+
diff --git a/.travis.yml b/.travis.yml
index 0642da6fe41..3dfbc53b8d1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ python:
- 2.7
- 3.1
- 3.2
+ - pypy
before_install:
- sudo apt-get install subversion bzr mercurial
- echo -e "[web]\ncacerts = /etc/ssl/certs/ca-certificates.crt" >> ~/.hgrc
@@ -15,5 +16,8 @@ notifications:
branches:
only:
- develop
+matrix:
+ allow_failures:
+ - python: 3.1
env:
- PIP_USE_MIRRORS=true
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 8dee04e4ecc..17d8c0a43de 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -1,11 +1,16 @@
Alex Grönholm
Alex Morega
Alexandre Conrad
+Andrey Bulgakov
Antti Kaihola
Armin Ronacher
+Aziz Köksal
+Ben Rosser
Brian Rosner
Carl Meyer
+Chris McDonough
Christian Oudard
+Clay McClure
Cody Soyland
Daniel Holth
Dave Abrahams
@@ -36,12 +41,16 @@ Nowell Strite
Oliver Tonnhofer
Olivier Girardot
Patrick Jenkins
+Paul Moore
Paul Nasrat
Paul Oswald
Paul van der Linden
Peter Waller
+Phil Whelan
Piet Delport
+Przemek Wrzos
Qiangning Hong
+Rafael Caricio
Rene Dudfield
Ronny Pfannschmidt
Rory McCann
diff --git a/LICENSE.txt b/LICENSE.txt
index 7951a03258d..c1024db5926 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2008-2011 The pip developers (see AUTHORS.txt file)
+Copyright (c) 2008-2012 The pip developers (see AUTHORS.txt file)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/README.rst b/README.rst
new file mode 100644
index 00000000000..251efd998aa
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,7 @@
+pip
+===
+
+.. image:: https://secure.travis-ci.org/pypa/pip.png?branch=develop
+ :target: http://travis-ci.org/pypa/pip
+
+For documentation, see http://www.pip-installer.org
diff --git a/docs/conf.py b/docs/conf.py
index 2c03957b156..8678de5f3f8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -11,12 +11,13 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys, os
+import os
+import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath(os.pardir))
#sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
# -- General configuration -----------------------------------------------------
@@ -40,15 +41,21 @@
# General information about the project.
project = 'pip'
-copyright = '2008-2011, The pip developers'
+copyright = '2008-2012, The pip developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-release = "1.1.post2"
-version = '.'.join(release.split('.')[:2])
+try:
+ from pip import __version__
+ # The short X.Y version.
+ version = '.'.join(__version__.split('.')[:2])
+ # The full version, including alpha/beta/rc tags.
+ release = __version__
+except ImportError:
+ version = release = 'dev'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/configuration.txt b/docs/configuration.txt
index 35d0a347f41..a1302b04d8f 100644
--- a/docs/configuration.txt
+++ b/docs/configuration.txt
@@ -84,7 +84,8 @@ Location
The names and locations of the configuration files vary slightly across
platforms.
-On Unix and Mac OS X the configuration file is: :file:`$HOME/.pip/pip.conf`
+On Unix and Mac OS X the configuration file is:
+:file:`$HOME/.config/pip/pip.conf`
And on Windows, the configuration file is: :file:`%HOME%\\pip\\pip.ini`
diff --git a/docs/contributing.txt b/docs/contributing.txt
index c3d0c135672..6caddd449db 100644
--- a/docs/contributing.txt
+++ b/docs/contributing.txt
@@ -60,10 +60,10 @@ Before sending us a pull request, please, be sure all tests pass.
Supported Python versions
-------------------------
-Pip supports Python versions 2.4, 2.5, 2.6, 2.7, 3.1, and 3.2, from a single
+Pip supports Python versions 2.5, 2.6, 2.7, 3.1, and 3.2, from a single
codebase (without use of 2to3 translation). Untested contributions frequently
-break Python 2.4 or 3.x compatibility. Please run the tests on at least 2.4 and
-3.2 and report your results when sending a pull request.
+break Python 3.x compatibility. Please run the tests on at least 3.2 and
+report your results when sending a pull request.
Continuous Integration server
-----------------------------
diff --git a/docs/news.txt b/docs/news.txt
index cbe1bb3fc41..ef84737dac5 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -5,13 +5,47 @@ News
Changelog
=========
-Next release (1.2) schedule
+Next release (1.3) schedule
---------------------------
-Beta and final releases planned for the second half of 2012.
+Beta and final releases planned for the end of 2012.
+
develop (unreleased)
--------------------
+--------------------
+
+* --user/--upgrade install options now work together; thanks eevee.
+
+* Added check in ``install --download`` to prevent re-downloading if the target
+ file already exists. Thanks Andrey Bulgakov.
+
+* Added support for bare paths (including relative paths) as argument to
+ `--find-links`. Thanks Paul Moore for draft patch.
+
+* Added support for --no-index in requirements files.
+
+* Added "pip show" command to get information about an installed
+ package. Fixes #131. Thanks Kelsey Hightower and Rafael Caricio.
+
+* Added `--root` option for "pip install" to specify root directory. Behaves
+ like the same option in distutils but also plays nice with pip's egg-info.
+ (Issue #253)
+
+1.2.1 (2012-09-06)
+------------------
+
+* Fixed a regression introduced in 1.2 about raising an exception when
+ not finding any files to uninstall in the current environment. Thanks for
+ the fix, Marcus Smith.
+
+1.2 (2012-09-01)
+----------------
+
+* **Dropped support for Python 2.4** The minimum supported Python version is
+ now Python 2.5.
+
+* Fixed issue #605 - pypi mirror support broken on some DNS responses. Thanks
+ philwhin.
* Fixed issue #355 - pip uninstall removes files it didn't install. Thanks
pjdelport.
@@ -26,7 +60,10 @@ develop (unreleased)
Hsiaoming Yang and Markus Hametner.
* Use a temporary directory as the default build location outside of a
- virtualenv. Fixes issues #339 and #381. Thanks TC01.
+ virtualenv. Fixes issues #339 and #381. Thanks Ben Rosser.
+
+* Moved pip configuration data to ~/.config/pip, the XDG standard for config
+ files on Unix/Linux systems. Thanks Ben Rosser.
* Added support for specifying extras with local editables. Thanks Nick
Stenning.
@@ -41,9 +78,6 @@ develop (unreleased)
* Fixed issue #504 - allow package URLS to have querystrings. Thanks W.
Trevor King.
-* Dropped support for Python 2.4. The minimum supported Python version is
- now Python 2.5.
-
* Fixed issue #58 - pip freeze now falls back to non-editable format rather
than blowing up if it can't determine the origin repository of an editable.
Thanks Rory McCann.
@@ -60,9 +94,21 @@ develop (unreleased)
* Fixed issue #427 - clearer error message on a malformed VCS url. Thanks
Thomas Fenzl.
-* Added support for using any of the built in guaranteed algorithms in ``hashlib``
- as a checksum hash.
+* Added support for using any of the built in guaranteed algorithms in
+ ``hashlib`` as a checksum hash.
+
+* Fixed issue #321 - Raise an exception if current working directory can't be
+ found or accessed.
+
+* Fixed issue #82 - Removed special casing of the user directory and use the
+ Python default instead.
+
+* Fixed #436 - Only warn about version conflicts if there is actually one.
+ This re-enables using ``==dev`` in requirements files.
+
+* Moved tests to be run on Travis CI: http://travis-ci.org/pypa/pip
+* Added a better help formatter.
1.1 (2012-02-16)
----------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 78502e46853..90491298d91 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -104,7 +104,7 @@ A line beginning with ``#`` is treated as a comment and ignored.
You can also request `extras`_ in the requirements file::
- MyPackage==3.0 [PDF]
+ MyPackage[PDF]==3.0
.. _extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
diff --git a/docs/usage.txt b/docs/usage.txt
index 283f25db1c9..02309f2d658 100644
--- a/docs/usage.txt
+++ b/docs/usage.txt
@@ -26,8 +26,8 @@ local or remote::
.. _setuptools extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
-Edit mode
-*********
+Editable mode
+*************
Packages normally_ install under ``site-packages``, but when you're
making changes, it makes more sense to run the package straight from the
@@ -147,9 +147,23 @@ packages. With the ``--index`` option you can search in a different
repository.
+Checking installed package status
+---------------------------------
+
+To get info about an installed package, including its location and included
+files, run ``pip show ProjectName``.
+
+
Bundles
-------
+.. note::
+
+ Pip bundles are poorly supported, poorly tested, not widely used, and
+ unnecessary (the same goals can be achieved by distributing a directory of
+ sdists and using `--find-links` to reference it). The feature will likely be
+ removed in a future version of pip.
+
Another way to distribute a set of libraries is a bundle format (specific to
pip). This format is not stable at this time (there simply hasn't been
any feedback, nor a great deal of thought). A bundle file contains all the
diff --git a/pip/__init__.py b/pip/__init__.py
index 62e3a910f76..e0de1c61a52 100755
--- a/pip/__init__.py
+++ b/pip/__init__.py
@@ -10,8 +10,12 @@
from pip.baseparser import parser
from pip.exceptions import InstallationError
from pip.log import logger
-from pip.util import get_installed_distributions
-from pip.vcs import git, mercurial, subversion, bazaar
+from pip.util import get_installed_distributions, get_prog
+from pip.vcs import git, mercurial, subversion, bazaar # noqa
+
+
+# The version as used in the setup.py and the docs conf.py
+__version__ = "1.2.1.post1"
def autocomplete():
@@ -26,7 +30,7 @@ def autocomplete():
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
- current = cwords[cword-1]
+ current = cwords[cword - 1]
except IndexError:
current = ''
load_all_commands()
@@ -59,7 +63,7 @@ def autocomplete():
for opt in subcommand.parser.option_list
if opt.help != optparse.SUPPRESS_HELP]
# filter out previously specified options from available options
- prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]]
+ prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [(x, v) for (x, v) in options if x not in prev_opts]
# filter options by current input
options = [(k, v) for k, v in options if k.startswith(current)]
@@ -87,7 +91,8 @@ def main(initial_args=None):
if options.help and not args:
args = ['help']
if not args:
- parser.error('You must give a command (use "pip help" to see a list of commands)')
+ parser.error('You must give a command '
+ '(use "%s help" to see a list of commands)' % get_prog())
command = args[0].lower()
load_command(command)
if command not in command_dict:
@@ -184,7 +189,7 @@ def __str__(self):
req = self.req
if self.editable:
req = '-e %s' % req
- return '\n'.join(list(self.comments)+[str(req)])+'\n'
+ return '\n'.join(list(self.comments) + [str(req)]) + '\n'
if __name__ == '__main__':
diff --git a/__main__.py b/pip/__main__.py
similarity index 53%
rename from __main__.py
rename to pip/__main__.py
index 258e87cf68a..5ca3746342c 100644
--- a/__main__.py
+++ b/pip/__main__.py
@@ -1,7 +1,7 @@
-if __name__ == '__main__':
- import sys
- from . import main
+import sys
+from .runner import run
- exit = main()
+if __name__ == '__main__':
+ exit = run()
if exit:
sys.exit(exit)
diff --git a/pip/backwardcompat.py b/pip/backwardcompat.py
index a8b3b922cf1..788023fae90 100644
--- a/pip/backwardcompat.py
+++ b/pip/backwardcompat.py
@@ -1,10 +1,14 @@
"""Stuff that differs in different Python versions"""
+import os
+import imp
import sys
import site
__all__ = ['WindowsError']
+uses_pycache = hasattr(imp,'cache_from_source')
+
try:
WindowsError = WindowsError
except NameError:
@@ -98,3 +102,11 @@ def product(*args, **kwds):
result = [x+[y] for x in result for y in pool]
for prod in result:
yield tuple(prod)
+
+def home_lib(home):
+ """Return the lib dir under the 'home' installation scheme"""
+ if hasattr(sys, 'pypy_version_info'):
+ lib = 'site-packages'
+ else:
+ lib = os.path.join('lib', 'python')
+ return os.path.join(home, lib)
diff --git a/pip/basecommand.py b/pip/basecommand.py
index d189d398708..48c390d9cef 100644
--- a/pip/basecommand.py
+++ b/pip/basecommand.py
@@ -4,6 +4,7 @@
from pkgutil import walk_packages
import socket
import sys
+import tempfile
import traceback
import time
@@ -15,6 +16,7 @@
CommandError)
from pip.backwardcompat import StringIO
from pip.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND
+from pip.util import get_prog
__all__ = ['command_dict', 'Command', 'load_all_commands',
@@ -35,7 +37,7 @@ def __init__(self):
assert self.name
self.parser = ConfigOptionParser(
usage=self.usage,
- prog='%s %s' % (sys.argv[0], self.name),
+ prog='%s %s' % (get_prog(), self.name),
version=parser.version,
formatter=UpdatingDefaultsHelpFormatter(),
name=self.name)
@@ -144,7 +146,7 @@ def main(self, args, initial_options):
temp = tempfile.NamedTemporaryFile(delete=False)
log_fn = temp.name
log_fp = open_logfile(log_fn, 'w')
- logger.fatal('Storing complete log in %s' % log_fn)
+ logger.fatal('Storing complete log in %s' % log_fn)
log_fp.write(text)
log_fp.close()
return exit
diff --git a/pip/baseparser.py b/pip/baseparser.py
index 0742ef6f67f..0ebdd8f393c 100644
--- a/pip/baseparser.py
+++ b/pip/baseparser.py
@@ -7,22 +7,17 @@
from distutils.util import strtobool
from pip.backwardcompat import ConfigParser, string_types
from pip.locations import default_config_file, default_log_file
+from pip.util import get_terminal_size, get_prog
-class PipPrettyHelpFormatter(optparse.IndentedHelpFormatter):
+class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
"""A prettier/less verbose help formatter for optparse."""
- def __init__(self, *args, **kw):
- kw['max_help_position'] = 23
- kw['indent_increment'] = 1
-
- # do as argparse does
- try:
- kw['width'] = int(os.environ['COLUMNS']) - 2
- except:
- kw['width'] = 78
-
- optparse.IndentedHelpFormatter.__init__(self, *args, **kw)
+ def __init__(self, *args, **kwargs):
+ kwargs['max_help_position'] = 30
+ kwargs['indent_increment'] = 1
+ kwargs['width'] = get_terminal_size()[0] - 2
+ optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
def format_option_strings(self, option):
return self._format_option_strings(option, ' <%s>', ', ')
@@ -35,31 +30,34 @@ def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '):
:param mvarfmt: metavar format string - evaluated as mvarfmt % metavar
:param optsep: separator
"""
-
opts = []
- if option._short_opts: opts.append(option._short_opts[0])
- if option._long_opts: opts.append(option._long_opts[0])
- if len(opts) > 1: opts.insert(1, optsep)
+ if option._short_opts:
+ opts.append(option._short_opts[0])
+ if option._long_opts:
+ opts.append(option._long_opts[0])
+ if len(opts) > 1:
+ opts.insert(1, optsep)
if option.takes_value():
metavar = option.metavar or option.dest.lower()
- opts.append(mvarfmt % metavar)
+ opts.append(mvarfmt % metavar.upper())
return ''.join(opts)
def format_heading(self, heading):
- if heading == 'Options': return ''
+ if heading == 'Options':
+ return ''
return heading + ':\n'
def format_usage(self, usage):
- # ensure there is only one newline between usage and the first heading
- # if there is no description
-
+ """
+ ensure there is only one newline between usage and the first heading
+ if there is no description
+ """
msg = 'Usage: %s' % usage
if self.parser.description:
msg += '\n'
-
return msg
def format_description(self, description):
@@ -77,7 +75,7 @@ def format_epilog(self, epilog):
return ''
-class UpdatingDefaultsHelpFormatter(PipPrettyHelpFormatter):
+class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
"""Custom help formatter for use in ConfigOptionParser that updates
the defaults before expanding them, allowing them to show up correctly
in the help listing"""
@@ -148,7 +146,7 @@ def normalize_keys(self, items):
for key, val in items:
key = key.replace('_', '-')
if not key.startswith('--'):
- key = '--%s' % key # only prefer long opts
+ key = '--%s' % key # only prefer long opts
normalized[key] = val
return normalized
@@ -171,7 +169,7 @@ def get_default_values(self):
# Old, pre-Optik 1.5 behaviour.
return optparse.Values(self.defaults)
- defaults = self.update_defaults(self.defaults.copy()) # ours
+ defaults = self.update_defaults(self.defaults.copy()) # ours
for option in self._get_all_options():
default = defaults.get(option.dest)
if isinstance(default, string_types):
@@ -179,20 +177,27 @@ def get_default_values(self):
defaults[option.dest] = option.check_value(opt_str, default)
return optparse.Values(defaults)
+ def error(self, msg):
+ self.print_usage(sys.stderr)
+ self.exit(2, "%s\n" % msg)
+
+
try:
pip_dist = pkg_resources.get_distribution('pip')
version = '%s from %s (python %s)' % (
pip_dist, pip_dist.location, sys.version[:3])
except pkg_resources.DistributionNotFound:
# when running pip.py without installing
- version=None
+ version = None
+
parser = ConfigOptionParser(
usage='%prog COMMAND [OPTIONS]',
version=version,
add_help_option=False,
formatter=UpdatingDefaultsHelpFormatter(),
- name='global')
+ name='global',
+ prog=get_prog())
parser.add_option(
'-h', '--help',
@@ -253,7 +258,7 @@ def get_default_values(self):
default='',
help="Specify a proxy in the form user:passwd@proxy.server:port. "
"Note that the user:password@ is optional and required only if you "
- "are behind an authenticated proxy. If you provide "
+ "are behind an authenticated proxy. If you provide "
"user@proxy.server:port then you will be prompted for a password.")
parser.add_option(
'--timeout', '--default-timeout',
@@ -285,10 +290,10 @@ def get_default_values(self):
choices=['s', 'i', 'w', 'b'],
default=[],
action='append',
- help="Default action when a path already exists."
- "Use this option more then one time to specify "
+ help="Default action when a path already exists. "
+ "Use this option more than one time to specify "
"another action if a certain option is not "
- "available, choices: "
+ "available. Choices: "
"(s)witch, (i)gnore, (w)ipe, (b)ackup")
parser.disable_interspersed_args()
diff --git a/pip/commands/install.py b/pip/commands/install.py
index 9c8252e0336..09ce935933f 100644
--- a/pip/commands/install.py
+++ b/pip/commands/install.py
@@ -9,6 +9,7 @@
from pip.basecommand import Command
from pip.index import PackageFinder
from pip.exceptions import InstallationError, CommandError
+from pip.backwardcompat import home_lib
class InstallCommand(Command):
@@ -36,7 +37,7 @@ def __init__(self):
action='append',
default=[],
metavar='FILENAME',
- help='Install all the packages listed in the given requirements file. '
+ help='Install all the packages listed in the given requirements file. '
'This option can be used multiple times.')
self.parser.add_option(
'-f', '--find-links',
@@ -153,15 +154,15 @@ def __init__(self):
dest='install_options',
action='append',
help="Extra arguments to be supplied to the setup.py install "
- "command (use like --install-option=\"--install-scripts=/usr/local/bin\"). "
- "Use multiple --install-option options to pass multiple options to setup.py install. "
+ "command (use like --install-option=\"--install-scripts=/usr/local/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.")
self.parser.add_option(
'--global-option',
dest='global_options',
action='append',
- help="Extra global options to be supplied to the setup.py"
+ help="Extra global options to be supplied to the setup.py "
"call before the install command")
self.parser.add_option(
@@ -176,6 +177,13 @@ def __init__(self):
action='store_true',
help="Install as self contained egg file, like easy_install does.")
+ self.parser.add_option(
+ '--root',
+ dest='root_path',
+ metavar='DIR',
+ default=None,
+ help="Install everything relative to this alternate root directory")
+
def _build_package_finder(self, options, index_urls):
"""
Create a package finder appropriate to this install command.
@@ -264,7 +272,7 @@ def run(self, options, args):
requirement_set.locate_files()
if not options.no_install and not self.bundle:
- requirement_set.install(install_options, global_options)
+ requirement_set.install(install_options, global_options, root=options.root_path)
installed = ' '.join([req.name for req in
requirement_set.successfully_installed])
if installed:
@@ -283,7 +291,7 @@ def run(self, options, args):
if options.target_dir:
if not os.path.exists(options.target_dir):
os.makedirs(options.target_dir)
- lib_dir = os.path.join(temp_target_dir, "lib/python/")
+ lib_dir = home_lib(temp_target_dir)
for item in os.listdir(lib_dir):
shutil.move(
os.path.join(lib_dir, item),
diff --git a/pip/commands/show.py b/pip/commands/show.py
new file mode 100644
index 00000000000..b5357f104e0
--- /dev/null
+++ b/pip/commands/show.py
@@ -0,0 +1,78 @@
+import os
+import pkg_resources
+from pip.basecommand import Command
+from pip.log import logger
+
+
+class ShowCommand(Command):
+ name = 'show'
+ usage = '%prog QUERY'
+ summary = 'Output installed distributions (exact versions, files) to stdout'
+
+ def __init__(self):
+ super(ShowCommand, self).__init__()
+ self.parser.add_option(
+ '-f', '--files',
+ dest='files',
+ action='store_true',
+ default=False,
+ help='Show the full list of installed files for each package')
+
+ def run(self, options, args):
+ if not args:
+ logger.warn('ERROR: Please provide a project name or names.')
+ return
+ query = args
+
+ results = search_packages_info(query)
+ print_results(results, options.files)
+
+
+def search_packages_info(query):
+ """
+ Gather details from installed distributions. Print distribution name,
+ version, location, and installed files. Installed files requires a
+ pip generated 'installed-files.txt' in the distributions '.egg-info'
+ directory.
+ """
+ installed_packages = dict(
+ [(p.project_name.lower(), p) for p in pkg_resources.working_set])
+ for name in query:
+ normalized_name = name.lower()
+ if normalized_name in installed_packages:
+ dist = installed_packages[normalized_name]
+ package = {
+ 'name': dist.project_name,
+ 'version': dist.version,
+ 'location': dist.location,
+ 'requires': [dep.project_name for dep in dist.requires()],
+ }
+ filelist = os.path.join(
+ dist.location,
+ dist.egg_name() + '.egg-info',
+ 'installed-files.txt')
+ if os.path.isfile(filelist):
+ package['files'] = filelist
+ yield package
+
+
+def print_results(distributions, list_all_files):
+ """
+ Print the informations from installed distributions found.
+ """
+ for dist in distributions:
+ logger.notify("---")
+ logger.notify("Name: %s" % dist['name'])
+ logger.notify("Version: %s" % dist['version'])
+ logger.notify("Location: %s" % dist['location'])
+ logger.notify("Requires: %s" % ', '.join(dist['requires']))
+ if list_all_files:
+ logger.notify("Files:")
+ if 'files' in dist:
+ for line in open(dist['files']):
+ logger.notify(" %s" % line.strip())
+ else:
+ logger.notify("Cannot locate installed-files.txt")
+
+
+ShowCommand()
diff --git a/pip/commands/zip.py b/pip/commands/zip.py
index ebe1d791a4d..1c84210acc9 100644
--- a/pip/commands/zip.py
+++ b/pip/commands/zip.py
@@ -231,7 +231,7 @@ def remove_filename_from_pth(self, filename):
def add_filename_to_pth(self, filename):
path = os.path.dirname(filename)
- dest = os.path.join(path, filename + '.pth')
+ dest = filename + '.pth'
if path not in self.paths():
logger.warn('Adding .pth file %s, but it is not on sys.path' % display_path(dest))
if not self.simulate:
diff --git a/pip/download.py b/pip/download.py
index 6243a5b48c6..b47627ecf9d 100644
--- a/pip/download.py
+++ b/pip/download.py
@@ -323,14 +323,14 @@ def is_file_url(link):
def _check_hash(download_hash, link):
- if download_hash.name != link.hash_name:
- logger.fatal("Hash name of the package %s (%s) doesn't match the expected hash name %s!"
- % (download_hash.name, link, link.hash_name))
+ if download_hash.digest_size != hashlib.new(link.hash_name).digest_size:
+ logger.fatal("Hash digest size of the package %d (%s) doesn't match the expected hash name %s!"
+ % (download_hash.digest_size, link, link.hash_name))
raise InstallationError('Hash name mismatch for package %s' % link)
if download_hash.hexdigest() != link.hash:
- logger.fatal("%s hash of the package %s (%s) doesn't match the expected hash %s!"
- % (download_hash.name, link, download_hash, link.hash))
- raise InstallationError('Bad %s hash for package %s' % (download_hash.name, link))
+ logger.fatal("Hash of the package %s (%s) doesn't match the expected hash %s!"
+ % (link, download_hash, link.hash))
+ raise InstallationError('Bad %s hash for package %s' % (link.hash_name, link))
def _get_hash_from_file(target_file, link):
@@ -374,7 +374,7 @@ def _download_url(resp, link, temp_location):
logger.start_progress('Downloading %s (unknown size): ' % show_url)
else:
logger.notify('Downloading %s' % show_url)
- logger.debug('Downloading from URL %s' % link)
+ logger.info('Downloading from URL %s' % link)
while True:
chunk = resp.read(4096)
@@ -429,6 +429,13 @@ def unpack_http_url(link, location, download_cache, download_dir=None):
urllib.quote(target_url, ''))
if not os.path.isdir(download_cache):
create_download_cache_folder(download_cache)
+
+ already_downloaded = None
+ if download_dir:
+ already_downloaded = os.path.join(download_dir, link.filename)
+ if not os.path.exists(already_downloaded):
+ already_downloaded = None
+
if (target_file
and os.path.exists(target_file)
and os.path.exists(target_file + '.content-type')):
@@ -439,6 +446,12 @@ def unpack_http_url(link, location, download_cache, download_dir=None):
download_hash = _get_hash_from_file(target_file, link)
temp_location = target_file
logger.notify('Using download cache from %s' % target_file)
+ elif already_downloaded:
+ temp_location = already_downloaded
+ content_type = mimetypes.guess_type(already_downloaded)
+ if link.hash:
+ download_hash = _get_hash_from_file(temp_location, link)
+ logger.notify('File was already downloaded %s' % already_downloaded)
else:
resp = _get_response_from_url(target_url, link)
content_type = resp.info()['content-type']
@@ -463,12 +476,12 @@ def unpack_http_url(link, location, download_cache, download_dir=None):
download_hash = _download_url(resp, link, temp_location)
if link.hash and link.hash_name:
_check_hash(download_hash, link)
- if download_dir:
+ if download_dir and not already_downloaded:
_copy_file(temp_location, download_dir, content_type, link)
unpack_file(temp_location, location, content_type, link)
if target_file and target_file != temp_location:
cache_download(target_file, temp_location, content_type)
- if target_file is None:
+ if target_file is None and not already_downloaded:
os.unlink(temp_location)
os.rmdir(temp_dir)
diff --git a/pip/index.py b/pip/index.py
index 7f3a50c8a6a..64059bc235e 100644
--- a/pip/index.py
+++ b/pip/index.py
@@ -20,7 +20,7 @@
from pip.util import normalize_name, splitext
from pip.exceptions import DistributionNotFound, BestVersionAlreadyInstalled
from pip.backwardcompat import (WindowsError, BytesIO,
- Queue, httplib, urlparse,
+ Queue, urlparse,
URLError, HTTPError, u,
product, url2pathname)
from pip.backwardcompat import Empty as QueueEmpty
@@ -29,7 +29,7 @@
__all__ = ['PackageFinder']
-DEFAULT_MIRROR_URL = "last.pypi.python.org"
+DEFAULT_MIRROR_HOSTNAME = "last.pypi.python.org"
class PackageFinder(object):
@@ -60,8 +60,7 @@ def add_dependency_links(self, links):
## FIXME: also, we should track comes_from (i.e., use Link)
self.dependency_links.extend(links)
- @staticmethod
- def _sort_locations(locations):
+ def _sort_locations(self, locations):
"""
Sort locations into "files" (archives) and "urls", and return
a pair of lists (files,urls)
@@ -69,8 +68,7 @@ def _sort_locations(locations):
files = []
urls = []
- # puts the url for the given file path into the appropriate
- # list
+ # puts the url for the given file path into the appropriate list
def sort_path(path):
url = path_to_url2(path)
if mimetypes.guess_type(url, strict=False)[0] == 'text/html':
@@ -79,25 +77,47 @@ def sort_path(path):
files.append(url)
for url in locations:
- if url.startswith('file:'):
- path = url_to_path(url)
- if os.path.isdir(path):
+
+ is_local_path = os.path.exists(url)
+ is_file_url = url.startswith('file:')
+ is_find_link = url in self.find_links
+
+ if is_local_path or is_file_url:
+ if is_local_path:
+ path = url
+ else:
+ path = url_to_path(url)
+ if is_find_link and os.path.isdir(path):
path = os.path.realpath(path)
for item in os.listdir(path):
sort_path(os.path.join(path, item))
+ elif is_file_url and os.path.isdir(path):
+ urls.append(url)
elif os.path.isfile(path):
sort_path(path)
else:
urls.append(url)
+
return files, urls
def find_requirement(self, req, upgrade):
+
+ def mkurl_pypi_url(url):
+ loc = posixpath.join(url, url_name)
+ # For maximum compatibility with easy_install, ensure the path
+ # ends in a trailing slash. Although this isn't in the spec
+ # (and PyPI can handle it without the slash) some other index
+ # implementations might break if they relied on easy_install's behavior.
+ if not loc.endswith('/'):
+ loc = loc + '/'
+ return loc
+
url_name = req.url_name
# Only check main index if index URL is given:
main_index_url = None
if self.index_urls:
# Check that we have the url_name correctly spelled:
- main_index_url = Link(posixpath.join(self.index_urls[0], url_name))
+ main_index_url = Link(mkurl_pypi_url(self.index_urls[0]))
# This will also cache the page, so it's okay that we get it again later:
page = self._get_page(main_index_url, req)
if page is None:
@@ -107,15 +127,6 @@ def find_requirement(self, req, upgrade):
# adding more index URLs from requirements files
all_index_urls = self.index_urls + self.mirror_urls
- def mkurl_pypi_url(url):
- loc = posixpath.join(url, url_name)
- # For maximum compatibility with easy_install, ensure the path
- # ends in a trailing slash. Although this isn't in the spec
- # (and PyPI can handle it without the slash) some other index
- # implementations might break if they relied on easy_install's behavior.
- if not loc.endswith('/'):
- loc = loc + '/'
- return loc
if url_name is not None:
locations = [
mkurl_pypi_url(url)
@@ -155,44 +166,46 @@ def mkurl_pypi_url(url):
if not found_versions and not page_versions and not dependency_versions and not file_versions:
logger.fatal('Could not find any downloads that satisfy the requirement %s' % req)
raise DistributionNotFound('No distributions at all found for %s' % req)
+ installed_version = []
if req.satisfied_by is not None:
- found_versions.append((req.satisfied_by.parsed_version, Inf, req.satisfied_by.version))
+ installed_version = [(req.satisfied_by.parsed_version, InfLink, req.satisfied_by.version)]
if file_versions:
file_versions.sort(reverse=True)
logger.info('Local files found: %s' % ', '.join([url_to_path(link.url) for parsed, link, version in file_versions]))
- found_versions = file_versions + found_versions
- all_versions = found_versions + page_versions + dependency_versions
+ #this is an intentional priority ordering
+ all_versions = installed_version + file_versions + found_versions + page_versions + dependency_versions
applicable_versions = []
for (parsed_version, link, version) in all_versions:
if version not in req.req:
logger.info("Ignoring link %s, version %s doesn't match %s"
% (link, version, ','.join([''.join(s) for s in req.req.specs])))
continue
- applicable_versions.append((link, version))
- applicable_versions = sorted(applicable_versions, key=lambda v: pkg_resources.parse_version(v[1]), reverse=True)
- existing_applicable = bool([link for link, version in applicable_versions if link is Inf])
+ applicable_versions.append((parsed_version, link, version))
+ #bring the latest version to the front, but maintains the priority ordering as secondary
+ applicable_versions = sorted(applicable_versions, key=lambda v: v[0], reverse=True)
+ existing_applicable = bool([link for parsed_version, link, version in applicable_versions if link is InfLink])
if not upgrade and existing_applicable:
- if applicable_versions[0][1] is Inf:
+ if applicable_versions[0][1] is InfLink:
logger.info('Existing installed version (%s) is most up-to-date and satisfies requirement'
% req.satisfied_by.version)
- raise BestVersionAlreadyInstalled
else:
logger.info('Existing installed version (%s) satisfies requirement (most up-to-date version is %s)'
- % (req.satisfied_by.version, applicable_versions[0][1]))
+ % (req.satisfied_by.version, applicable_versions[0][2]))
return None
if not applicable_versions:
logger.fatal('Could not find a version that satisfies the requirement %s (from versions: %s)'
- % (req, ', '.join([version for parsed_version, link, version in found_versions])))
+ % (req, ', '.join([version for parsed_version, link, version in all_versions])))
raise DistributionNotFound('No distributions matching the version for %s' % req)
- if applicable_versions[0][0] is Inf:
+ if applicable_versions[0][1] is InfLink:
# We have an existing version, and its the best version
logger.info('Installed version (%s) is most up-to-date (past versions: %s)'
- % (req.satisfied_by.version, ', '.join([version for link, version in applicable_versions[1:]]) or 'none'))
+ % (req.satisfied_by.version, ', '.join([version for parsed_version, link, version in applicable_versions[1:]]) or 'none'))
raise BestVersionAlreadyInstalled
if len(applicable_versions) > 1:
logger.info('Using version %s (newest of versions: %s)' %
- (applicable_versions[0][1], ', '.join([version for link, version in applicable_versions])))
- return applicable_versions[0][0]
+ (applicable_versions[0][2], ', '.join([version for parsed_version, link, version in applicable_versions])))
+ return applicable_versions[0][1]
+
def _find_url_name(self, index_url, url_name, req):
"""Finds the true URL name of a package, when the given name isn't quite correct.
@@ -215,7 +228,7 @@ def _find_url_name(self, index_url, url_name, req):
def _get_pages(self, locations, req):
"""Yields (page, page_url) from the given locations, skipping
- locations that have errors, and adding download/homepage links"""
+ locations that have errors, and adding homepage links"""
pending_queue = Queue()
for location in locations:
pending_queue.put(location)
@@ -246,7 +259,7 @@ def _get_queued_page(self, req, pending_queue, done, seen):
if page is None:
continue
done.append(page)
- for link in page.rel_links():
+ for link in page.rel_links(rels=('homepage',)):
pending_queue.put(link)
_egg_fragment_re = re.compile(r'#egg=([^&]*)')
@@ -530,8 +543,8 @@ def links(self):
url = self.clean_link(urlparse.urljoin(self.base_url, url))
yield Link(url, self)
- def rel_links(self):
- for url in self.explicit_rel_links():
+ def rel_links(self, rels=('homepage', 'download')):
+ for url in self.explicit_rel_links(rels):
yield url
for url in self.scraped_rel_links():
yield url
@@ -586,7 +599,7 @@ def __str__(self):
if self.comes_from:
return '%s (from %s)' % (self.url, self.comes_from)
else:
- return self.url
+ return str(self.url)
def __repr__(self):
return '' % self
@@ -594,6 +607,21 @@ def __repr__(self):
def __eq__(self, other):
return self.url == other.url
+ def __ne__(self, other):
+ return self.url != other.url
+
+ def __lt__(self, other):
+ return self.url < other.url
+
+ def __le__(self, other):
+ return self.url <= other.url
+
+ def __gt__(self, other):
+ return self.url > other.url
+
+ def __ge__(self, other):
+ return self.url >= other.url
+
def __hash__(self):
return hash(self.url)
@@ -649,6 +677,9 @@ def hash_name(self):
def show_url(self):
return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0])
+#An "Infinite Link" that compares greater than other links
+InfLink = Link(Inf) #this object is not currently used as a sortable
+
def get_requirement_from_url(url):
"""Get a requirement from the URL, if possible. This looks for #egg
@@ -687,14 +718,17 @@ def get_mirrors(hostname=None):
Originally written for the distutils2 project by Alexis Metaireau.
"""
if hostname is None:
- hostname = DEFAULT_MIRROR_URL
+ hostname = DEFAULT_MIRROR_HOSTNAME
# return the last mirror registered on PyPI.
+ last_mirror_hostname = None
try:
- hostname = socket.gethostbyname_ex(hostname)[0]
+ last_mirror_hostname = socket.gethostbyname_ex(hostname)[0]
except socket.gaierror:
return []
- end_letter = hostname.split(".", 1)
+ if not last_mirror_hostname or last_mirror_hostname == DEFAULT_MIRROR_HOSTNAME:
+ last_mirror_hostname = "z.pypi.python.org"
+ end_letter = last_mirror_hostname.split(".", 1)
# determine the list from the last one.
return ["%s.%s" % (s, end_letter[1]) for s in string_range(end_letter[0])]
diff --git a/pip/locations.py b/pip/locations.py
index 976538e7ac5..996b00d23c5 100644
--- a/pip/locations.py
+++ b/pip/locations.py
@@ -3,6 +3,7 @@
import sys
import site
import os
+import shutil
import tempfile
from pip.backwardcompat import get_python_lib
@@ -14,26 +15,26 @@ def running_under_virtualenv():
"""
return hasattr(sys, 'real_prefix')
+
def virtualenv_no_global():
"""
Return True if in a venv and no system site packages.
"""
#this mirrors the logic in virtualenv.py for locating the no-global-site-packages.txt file
site_mod_dir = os.path.dirname(os.path.abspath(site.__file__))
- no_global_file = os.path.join(site_mod_dir,'no-global-site-packages.txt')
+ 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
if running_under_virtualenv():
- ## FIXME: is build/ a good name?
build_prefix = os.path.join(sys.prefix, 'build')
src_prefix = os.path.join(sys.prefix, 'src')
else:
# Use tempfile to create a temporary folder for build
# Note: we are NOT using mkdtemp so we can have a consistent build dir
build_prefix = os.path.join(tempfile.gettempdir(), 'pip-build')
-
+
## FIXME: keep src in cwd for now (it is not a temporary folder)
try:
src_prefix = os.path.join(os.getcwd(), 'src')
@@ -60,9 +61,29 @@ def virtualenv_no_global():
default_log_file = os.path.join(default_storage_dir, 'pip.log')
else:
bin_py = os.path.join(sys.prefix, 'bin')
- default_storage_dir = os.path.join(user_dir, '.pip')
+
+ # Use XDG_CONFIG_HOME instead of the ~/.pip
+ # On some systems, we may have to create this, on others it probably exists
+ xdg_dir = os.path.join(user_dir, '.config')
+ xdg_dir = os.environ.get('XDG_CONFIG_HOME', xdg_dir)
+ if not os.path.exists(xdg_dir):
+ os.mkdir(xdg_dir)
+ default_storage_dir = os.path.join(xdg_dir, 'pip')
default_config_file = os.path.join(default_storage_dir, 'pip.conf')
default_log_file = os.path.join(default_storage_dir, 'pip.log')
+
+ # Migration path for users- move things from the old dir if it exists
+ # If the new dir exists and has no pip.conf and the old dir does, move it
+ # When these checks are finished, delete the old directory
+ old_storage_dir = os.path.join(user_dir, '.pip')
+ old_config_file = os.path.join(old_storage_dir, 'pip.conf')
+ if os.path.exists(old_storage_dir):
+ if not os.path.exists(default_storage_dir):
+ shutil.copytree(old_storage_dir, default_storage_dir)
+ elif os.path.exists(old_config_file) and not os.path.exists(default_config_file):
+ shutil.copy2(old_config_file, default_config_file)
+ shutil.rmtree(old_storage_dir)
+
# Forcing to use /usr/local/bin for standard Mac OS X framework installs
# Also log to ~/Library/Logs/ for use with the Console.app log viewer
if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
diff --git a/pip/req.py b/pip/req.py
index da1c29e50f8..898c61584fe 100644
--- a/pip/req.py
+++ b/pip/req.py
@@ -1,5 +1,6 @@
from email.parser import FeedParser
import os
+import imp
import pkg_resources
import re
import sys
@@ -7,6 +8,7 @@
import tempfile
import zipfile
+from distutils.util import change_root
from pip.locations import bin_py, running_under_virtualenv
from pip.exceptions import (InstallationError, UninstallationError,
BestVersionAlreadyInstalled,
@@ -19,7 +21,7 @@
from pip.util import renames, normalize_path, egg_link_path, dist_in_site_packages
from pip.util import make_path_relative
from pip.util import call_subprocess
-from pip.backwardcompat import (urlparse, urllib,
+from pip.backwardcompat import (urlparse, urllib, uses_pycache,
ConfigParser, string_types, HTTPError,
get_python_version, b)
from pip.index import Link
@@ -97,7 +99,7 @@ def from_line(cls, name, comes_from=None, upgrade=False):
link = Link(name)
elif os.path.isdir(path) and (os.path.sep in name or name.startswith('.')):
if not is_installable_dir(path):
- raise InstallationError("Directory %r is not installable. File 'setup.py' not found.", name)
+ raise InstallationError("Directory %r is not installable. File 'setup.py' not found." % name)
link = Link(path_to_url(name))
elif is_archive_file(path):
if not os.path.isfile(path):
@@ -108,7 +110,7 @@ def from_line(cls, name, comes_from=None, upgrade=False):
# Otherwise, assume the name is the req for the non URL/path/archive case.
if link and req is None:
url = link.url_without_fragment
- req = link.egg_fragment
+ req = link.egg_fragment #when fragment is None, this will become an 'unnamed' requirement
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', url):
@@ -370,18 +372,9 @@ def installed_version(self):
def assert_source_matches_version(self):
assert self.source_dir
- if self.comes_from is None:
- # We don't check the versions of things explicitly installed.
- # This makes, e.g., "pip Package==dev" possible
- return
version = self.installed_version
if version not in self.req:
- logger.fatal(
- 'Source in %s has the version %s, which does not match the requirement %s'
- % (display_path(self.source_dir), version, self))
- raise InstallationError(
- 'Source in %s has version %s that conflicts with %s'
- % (display_path(self.source_dir), version, self))
+ logger.warn('Requested %s, but installing version %s' % (self, self.installed_version))
else:
logger.debug('Source in %s has version %s, which satisfies requirement %s'
% (display_path(self.source_dir), version, self))
@@ -451,6 +444,8 @@ def uninstall(self, auto_confirm=False):
for installed_file in dist.get_metadata('installed-files.txt').splitlines():
path = os.path.normpath(os.path.join(egg_info_path, installed_file))
paths_to_remove.add(path)
+ #FIXME: need a test for this elif block
+ #occurs with --single-version-externally-managed/--record outside of pip
elif dist.has_metadata('top_level.txt'):
if dist.has_metadata('namespace_packages.txt'):
namespaces = dist.get_metadata('namespace_packages.txt')
@@ -566,7 +561,7 @@ def _clean_zip_name(self, name, prefix):
name = name.replace(os.path.sep, '/')
return name
- def install(self, install_options, global_options=()):
+ def install(self, install_options, global_options=(), root=None):
if self.editable:
self.install_editable(install_options, global_options)
return
@@ -585,6 +580,9 @@ def install(self, install_options, global_options=()):
if not self.as_egg:
install_args += ['--single-version-externally-managed']
+ if root is not None:
+ install_args += ['--root', root]
+
if running_under_virtualenv():
## FIXME: I'm not sure if this is a reasonable location; probably not
## but we can't put it in the default location, as that is a virtualenv symlink that isn't writable
@@ -607,11 +605,17 @@ def install(self, install_options, global_options=()):
# so we unable to save the installed-files.txt
return
+ def prepend_root(path):
+ if root is None or not os.path.isabs(path):
+ return path
+ else:
+ return change_root(root, path)
+
f = open(record_filename)
for line in f:
line = line.strip()
if line.endswith('.egg-info'):
- egg_info_dir = line
+ egg_info_dir = prepend_root(line)
break
else:
logger.warn('Could not find .egg-info directory in install record for %s' % self)
@@ -625,7 +629,7 @@ def install(self, install_options, global_options=()):
filename = line.strip()
if os.path.isdir(filename):
filename += os.path.sep
- new_lines.append(make_path_relative(filename, egg_info_dir))
+ new_lines.append(make_path_relative(prepend_root(filename), egg_info_dir))
f.close()
f = open(os.path.join(egg_info_dir, 'installed-files.txt'), 'w')
f.write('\n'.join(new_lines)+'\n')
@@ -842,6 +846,7 @@ def add_requirement(self, install_req):
install_req.as_egg = self.as_egg
install_req.use_user_site = self.use_user_site
if not name:
+ #url or path requirement w/o an egg fragment
self.unnamed_requirements.append(install_req)
else:
if self.has_requirement(name):
@@ -898,7 +903,7 @@ def uninstall(self, auto_confirm=False):
req.commit_uninstall()
def locate_files(self):
- ## FIXME: duplicates code from install_files; relevant code should
+ ## FIXME: duplicates code from prepare_files; relevant code should
## probably be factored out into a separate method
unnamed = list(self.unnamed_requirements)
reqs = list(self.requirements.values())
@@ -911,8 +916,10 @@ def locate_files(self):
if not self.ignore_installed and not req_to_install.editable:
req_to_install.check_if_exists()
if req_to_install.satisfied_by:
- if req_to_install.upgrade:
- req_to_install.conflicts_with = req_to_install.satisfied_by
+ if self.upgrade:
+ #don't uninstall conflict if user install and and conflict is not user install
+ if not (self.use_user_site and not dist_in_usersite(req_to_install.satisfied_by)):
+ req_to_install.conflicts_with = req_to_install.satisfied_by
req_to_install.satisfied_by = None
else:
install_needed = False
@@ -968,7 +975,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
req_to_install.url = url.url
if not best_installed:
- req_to_install.conflicts_with = req_to_install.satisfied_by
+ #don't uninstall conflict if user install and conflict is not user install
+ if not (self.use_user_site and not dist_in_usersite(req_to_install.satisfied_by)):
+ req_to_install.conflicts_with = req_to_install.satisfied_by
req_to_install.satisfied_by = None
else:
install = False
@@ -1014,7 +1023,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
##occurs when the script attempts to unpack the
##build directory
+ # NB: This call can result in the creation of a temporary build directory
location = req_to_install.build_location(self.build_dir, not self.is_download)
+
## FIXME: is the existance of the checkout good enough to use it? I don't think so.
unpack = True
url = None
@@ -1069,7 +1080,9 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
req_to_install.check_if_exists()
if req_to_install.satisfied_by:
if req_to_install.upgrade or self.ignore_installed:
- req_to_install.conflicts_with = req_to_install.satisfied_by
+ #don't uninstall conflict if user install and and conflict is not user install
+ if not (self.use_user_site and not dist_in_usersite(req_to_install.satisfied_by)):
+ req_to_install.conflicts_with = req_to_install.satisfied_by
req_to_install.satisfied_by = None
else:
install = False
@@ -1093,9 +1106,10 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False):
subreq = InstallRequirement(req, req_to_install, upgrade=self.upgrade_recursive)
reqs.append(subreq)
self.add_requirement(subreq)
- if req_to_install.name not in self.requirements:
- self.requirements[req_to_install.name] = req_to_install
- if self.is_download:
+ if not self.has_requirement(req_to_install.name):
+ #'unnamed' requirements will get added here
+ self.add_requirement(req_to_install)
+ if self.is_download or req_to_install._temp_build_dir is not None:
self.reqs_to_cleanup.append(req_to_install)
else:
self.reqs_to_cleanup.append(req_to_install)
@@ -1149,7 +1163,8 @@ def unpack_url(self, link, location, only_download=False):
loc = location
if is_vcs_url(link):
return unpack_vcs_link(link, loc, only_download)
- elif is_file_url(link):
+ # a local file:// index could have links with hashes
+ elif not link.hash and is_file_url(link):
return unpack_file_url(link, loc)
else:
if self.download_cache:
@@ -1159,7 +1174,7 @@ def unpack_url(self, link, location, only_download=False):
_write_delete_marker_message(os.path.join(location, PIP_DELETE_MARKER_FILENAME))
return retval
- def install(self, install_options, global_options=()):
+ def install(self, install_options, global_options=(), *args, **kwargs):
"""Install everything in this set (after having downloaded and unpacked the packages)"""
to_install = [r for r in self.requirements.values()
if not r.satisfied_by]
@@ -1178,7 +1193,7 @@ def install(self, install_options, global_options=()):
finally:
logger.indent -= 2
try:
- requirement.install(install_options, global_options)
+ requirement.install(install_options, global_options, *args, **kwargs)
except:
# if install did not succeed, rollback previous uninstall
if requirement.conflicts_with and not requirement.install_succeeded:
@@ -1285,9 +1300,10 @@ def _write_delete_marker_message(filepath):
def parse_requirements(filename, finder=None, comes_from=None, options=None):
skip_match = None
- skip_regex = options.skip_requirements_regex
+ skip_regex = options.skip_requirements_regex if options else None
if skip_regex:
skip_match = re.compile(skip_regex)
+ reqs_file_dir = os.path.dirname(os.path.abspath(filename))
filename, content = get_file_content(filename, comes_from=comes_from)
for line_number, line in enumerate(content.splitlines()):
line_number += 1
@@ -1319,6 +1335,10 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None):
line = line[len('--find-links'):].strip().lstrip('=')
## FIXME: it would be nice to keep track of the source of
## the find_links:
+ # support a find-links local path relative to a requirements file
+ relative_to_reqs_file = os.path.join(reqs_file_dir, line)
+ if os.path.exists(relative_to_reqs_file):
+ line = relative_to_reqs_file
if finder:
finder.find_links.append(line)
elif line.startswith('-i') or line.startswith('--index-url'):
@@ -1332,6 +1352,8 @@ def parse_requirements(filename, finder=None, comes_from=None, options=None):
line = line[len('--extra-index-url'):].strip().lstrip('=')
if finder:
finder.index_urls.append(line)
+ elif line.startswith('--no-index'):
+ finder.index_urls = []
else:
# Use getattr since uninstall doesn't set these options
upgrade = (getattr(options, 'upgrade', False)
@@ -1368,7 +1390,7 @@ def parse_editable(editable_req, default_vcs=None):
if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
- raise InstallationError("Directory %r is not installable. File 'setup.py' not found.", url_no_extras)
+ raise InstallationError("Directory %r is not installable. File 'setup.py' not found." % url_no_extras)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
@@ -1449,6 +1471,11 @@ def add(self, path):
else:
self._refuse.add(path)
+ # __pycache__ files can show up after 'installed-files.txt' is created, due to imports
+ if os.path.splitext(path)[1] == '.py' and uses_pycache:
+ self.add(imp.cache_from_source(path))
+
+
def add_pth(self, pth_file, entry):
pth_file = normalize_path(pth_file)
if self._permitted(pth_file):
@@ -1478,10 +1505,11 @@ def _stash(self, path):
def remove(self, auto_confirm=False):
"""Remove paths in ``self.paths`` with confirmation (unless
``auto_confirm`` is True)."""
- if not self.paths:
- raise InstallationError("Can't uninstall '%s'. No files were found to uninstall." % self.dist.project_name)
if not self._can_uninstall():
return
+ if not self.paths:
+ logger.notify("Can't uninstall '%s'. No files were found to uninstall." % self.dist.project_name)
+ return
logger.notify('Uninstalling %s:' % self.dist.project_name)
logger.indent += 2
paths = sorted(self.compact(self.paths))
diff --git a/pip/util.py b/pip/util.py
index 833a7adcc7a..c37432da40c 100644
--- a/pip/util.py
+++ b/pip/util.py
@@ -20,11 +20,20 @@
'is_svn_page', 'file_contents',
'split_leading_dir', 'has_leading_dir',
'make_path_relative', 'normalize_path',
- 'renames', 'get_terminal_size',
+ 'renames', 'get_terminal_size', 'get_prog',
'unzip_file', 'untar_file', 'create_download_cache_folder',
'cache_download', 'unpack_file', 'call_subprocess']
+def get_prog():
+ try:
+ if os.path.basename(sys.argv[0]) in ('__main__.py', '-c'):
+ return "%s -m pip" % sys.executable
+ except (AttributeError, TypeError, IndexError):
+ pass
+ return 'pip'
+
+
def rmtree(dir, ignore_errors=False):
shutil.rmtree(dir, ignore_errors=ignore_errors,
onerror=rmtree_errorhandler)
@@ -78,7 +87,7 @@ def find_command(cmd, paths=None, pathext=None):
# check if there are funny path extensions for executables, e.g. Windows
if pathext is None:
pathext = get_pathext()
- pathext = [ext for ext in pathext.lower().split(os.pathsep)]
+ pathext = [ext for ext in pathext.lower().split(os.pathsep) if len(ext)]
# don't use extensions if the command ends with one of them
if os.path.splitext(cmd)[1].lower() in pathext:
pathext = ['']
@@ -127,15 +136,33 @@ def ask(message, options):
class _Inf(object):
"""I am bigger than everything!"""
- def __cmp__(self, a):
- if self is a:
- return 0
- return 1
+
+ def __eq__(self, other):
+ if self is other:
+ return True
+ else:
+ return False
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __lt__(self, other):
+ return False
+
+ def __le__(self, other):
+ return False
+
+ def __gt__(self, other):
+ return True
+
+ def __ge__(self, other):
+ return True
def __repr__(self):
return 'Inf'
-Inf = _Inf()
+
+Inf = _Inf() #this object is not currently used as a sortable in our code
del _Inf
@@ -344,8 +371,7 @@ def egg_link_path(dist):
For #1 and #3, there could be odd cases, where there's an egg-link in 2 locations.
This method will just return the first one found.
"""
-
- sites=[]
+ sites = []
if running_under_virtualenv():
if virtualenv_no_global():
sites.append(site_packages)
diff --git a/pip/vcs/git.py b/pip/vcs/git.py
index b90cc1ef732..abb57ac7a81 100644
--- a/pip/vcs/git.py
+++ b/pip/vcs/git.py
@@ -214,8 +214,7 @@ def _get_revision_from_rev_parse(self, name, location):
def update_submodules(self, location):
if not os.path.exists(os.path.join(location, '.gitmodules')):
return
- call_subprocess([self.cmd, 'submodule', 'init', '-q'], cwd=location)
- call_subprocess([self.cmd, 'submodule', 'update', '--recursive', '-q'],
+ call_subprocess([self.cmd, 'submodule', 'update', '--init', '--recursive', '-q'],
cwd=location)
vcs.register(Git)
diff --git a/setup.cfg b/setup.cfg
index ce26f6afe4d..cffcdfd46ba 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,5 @@
[nosetests]
where=tests
+
+[aliases]
+dev = develop easy_install pip[testing]
diff --git a/setup.py b/setup.py
index 95a7c68ab35..4ccc1025b2b 100644
--- a/setup.py
+++ b/setup.py
@@ -1,32 +1,39 @@
-import sys
+import codecs
import os
+import re
+import sys
from setuptools import setup
-# If you change this version, change it also in docs/conf.py
-version = "1.1.post2"
-doc_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "docs")
-index_filename = os.path.join(doc_dir, "index.txt")
-news_filename = os.path.join(doc_dir, "news.txt")
+def read(*parts):
+ return codecs.open(os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts), 'r').read()
+
+
+def find_version(*file_paths):
+ version_file = read(*file_paths)
+ version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
+ version_file, re.M)
+ if version_match:
+ return version_match.group(1)
+ raise RuntimeError("Unable to find version string.")
+
long_description = """
The main website for pip is `www.pip-installer.org
-`_. You can also install
+`_. You can also install
the `in-development version `_
of pip with ``easy_install pip==dev``.
"""
-f = open(index_filename)
# remove the toctree from sphinx index, as it breaks long_description
-parts = f.read().split("split here", 2)
-long_description = parts[0] + long_description + parts[2]
-f.close()
-f = open(news_filename)
-long_description += "\n\n" + f.read()
-f.close()
+parts = read("docs", "index.txt").split("split here", 2)
+long_description = (parts[0] + long_description + parts[2] +
+ "\n\n" + read("docs", "news.txt"))
+
+tests_require = ['nose', 'virtualenv>=1.7', 'scripttest>=1.1.1', 'mock']
setup(name="pip",
- version=version,
+ version=find_version('pip', '__init__.py'),
description="pip installs packages. Python packages. An easy_install replacement",
long_description=long_description,
classifiers=[
@@ -35,7 +42,6 @@
'License :: OSI Approved :: MIT License',
'Topic :: Software Development :: Build Tools',
'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.4',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
@@ -51,5 +57,9 @@
packages=['pip', 'pip.commands', 'pip.vcs'],
entry_points=dict(console_scripts=['pip=pip:main', 'pip-%s=pip:main' % sys.version[:3]]),
test_suite='nose.collector',
- tests_require=['nose', 'virtualenv>=1.7', 'scripttest>=1.1.1', 'mock'],
- zip_safe=False)
+ tests_require=tests_require,
+ zip_safe=False,
+ extras_require = {
+ 'testing':tests_require,
+ },
+ )
diff --git a/tests/in dex/FSPkg/FSPkg-0.1dev.tar.gz b/tests/in dex/FSPkg/FSPkg-0.1dev.tar.gz
deleted file mode 100644
index 7fa7c10c239..00000000000
Binary files a/tests/in dex/FSPkg/FSPkg-0.1dev.tar.gz and /dev/null differ
diff --git a/tests/in dex/FSPkg/index.html b/tests/in dex/FSPkg/index.html
deleted file mode 100644
index cf4f404e32f..00000000000
--- a/tests/in dex/FSPkg/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-Links for FSPkg
Links for FSPkg
FSPkg-0.1dev.tar.gz
-Source
-
diff --git a/tests/indexes/README.txt b/tests/indexes/README.txt
new file mode 100644
index 00000000000..8e430effdba
--- /dev/null
+++ b/tests/indexes/README.txt
@@ -0,0 +1,15 @@
+
+Details on Test Indexes
+=======================
+
+empty_with_pkg
+--------------
+empty index, but there's a package in the dir
+
+in dex
+------
+for testing url quoting with indexes
+
+simple
+------
+contains index page for "simple" pkg
diff --git a/tests/indexes/empty_with_pkg/index.html b/tests/indexes/empty_with_pkg/index.html
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/indexes/empty_with_pkg/simple-1.0.tar.gz b/tests/indexes/empty_with_pkg/simple-1.0.tar.gz
new file mode 100644
index 00000000000..6d9f2dd8b34
Binary files /dev/null and b/tests/indexes/empty_with_pkg/simple-1.0.tar.gz differ
diff --git a/tests/in dex/README.txt b/tests/indexes/in dex/README.txt
similarity index 100%
rename from tests/in dex/README.txt
rename to tests/indexes/in dex/README.txt
diff --git a/tests/indexes/in dex/simple/index.html b/tests/indexes/in dex/simple/index.html
new file mode 100644
index 00000000000..dba6cc3ebd6
--- /dev/null
+++ b/tests/indexes/in dex/simple/index.html
@@ -0,0 +1,5 @@
+
+
+ simple-1.0.tar.gz
+
+
diff --git a/tests/indexes/simple/simple/index.html b/tests/indexes/simple/simple/index.html
new file mode 100644
index 00000000000..dba6cc3ebd6
--- /dev/null
+++ b/tests/indexes/simple/simple/index.html
@@ -0,0 +1,5 @@
+
+
+ simple-1.0.tar.gz
+
+
diff --git a/tests/packages/LocalExtras/setup.py b/tests/packages/LocalExtras/setup.py
index 646c1671f57..c7965c30354 100644
--- a/tests/packages/LocalExtras/setup.py
+++ b/tests/packages/LocalExtras/setup.py
@@ -2,12 +2,12 @@
from setuptools import setup, find_packages
HERE = os.path.dirname(__file__)
-INDEX = os.path.join(HERE, '..', '..', 'in dex', 'FSPkg')
+INDEX = "file://" + os.path.join(HERE, '..', '..', 'indexes', 'simple', 'simple')
setup(
name='LocalExtras',
version='0.0.1',
packages=find_packages(),
- extras_require={ 'bar': ['FSPkg'] },
- dependency_links=['file://' + INDEX]
+ extras_require={ 'bar': ['simple'] },
+ dependency_links=[INDEX]
)
diff --git a/tests/packages/README.txt b/tests/packages/README.txt
index 069b32c1437..fdf925d6e96 100644
--- a/tests/packages/README.txt
+++ b/tests/packages/README.txt
@@ -1,9 +1,75 @@
-This package exists for testing uninstall-rollback.
+Details on Test Packages
+========================
+
+broken-0.1.tar.gz
+-----------------
+This package exists for testing uninstall-rollback.
+
+broken-0.2broken.tar.gz
+-----------------------
Version 0.2broken has a setup.py crafted to fail on install (and only on
install). If any earlier step would fail (i.e. egg-info-generation), the
already-installed version would never be uninstalled, so uninstall-rollback
would not come into play.
+BrokenEmitsUTF8
+---------------
+for generating unicode error in py3.x
+
+duplicate-1.0.tar.gz
+--------------------
+for testing finding dupes across multiple find-links
+
+FSPkg
+-----
+for installing from the file system
+
+gmpy-1.15.tar.gz
+----------------
+hash testing (altough this pkg isn't needed explicitly)
+
+gmpy2-2.0.tar.gz
+----------------
+for testing finder logic when name *contains* the name of the package specified
+
+HackedEggInfo
+-------------
+has it's own egg_info class
+
+LineEndings
+-----------
+contains DOS line endings
+
+LocalExtras
+-----------
+has an extra in a local file:// dependency link
+
+parent/child-0.1.tar.gz
+-----------------------
The parent-0.1.tar.gz and child-0.1.tar.gz packages are used by
test_uninstall:test_uninstall_overlapping_package.
+
+paxpkg.tar.bz2
+--------------
+tar with pax headers
+
+pkgwithmpkg-1.0.tar.gz; pkgwithmpkg-1.0-py2.7-macosx10.7.mpkg.zip
+-----------------------------------------------------------------
+used for osx test case (tests.test_finder:test_no_mpkg)
+
+simple-[123].0.tar.gz
+---------------------
+contains "simple" package; good for basic testing and version logic.
+
+Upper-[12].0.tar.gz and requiresuppper-1.0.tar.gz
+--------------------------------------------------
+'requiresupper' requires 'upper'
+used for testing case mismatch case for url requirements
+
+
+
+
+
+
+
diff --git a/tests/packages/Upper-1.0.tar.gz b/tests/packages/Upper-1.0.tar.gz
new file mode 100644
index 00000000000..e0d28fdc762
Binary files /dev/null and b/tests/packages/Upper-1.0.tar.gz differ
diff --git a/tests/packages/Upper-2.0.tar.gz b/tests/packages/Upper-2.0.tar.gz
new file mode 100644
index 00000000000..81ff441daa6
Binary files /dev/null and b/tests/packages/Upper-2.0.tar.gz differ
diff --git a/tests/packages/duplicate-1.0.tar.gz b/tests/packages/duplicate-1.0.tar.gz
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/packages/parent-0.1.tar.gz b/tests/packages/parent-0.1.tar.gz
index 7d8673cacaa..130e756f491 100644
Binary files a/tests/packages/parent-0.1.tar.gz and b/tests/packages/parent-0.1.tar.gz differ
diff --git a/tests/packages/requiresupper-1.0.tar.gz b/tests/packages/requiresupper-1.0.tar.gz
new file mode 100644
index 00000000000..fe121a90f23
Binary files /dev/null and b/tests/packages/requiresupper-1.0.tar.gz differ
diff --git a/tests/packages/simple-1.0.tar.gz b/tests/packages/simple-1.0.tar.gz
new file mode 100644
index 00000000000..6d9f2dd8b34
Binary files /dev/null and b/tests/packages/simple-1.0.tar.gz differ
diff --git a/tests/packages/simple-2.0.tar.gz b/tests/packages/simple-2.0.tar.gz
new file mode 100644
index 00000000000..c53a523f145
Binary files /dev/null and b/tests/packages/simple-2.0.tar.gz differ
diff --git a/tests/packages/simple-3.0.tar.gz b/tests/packages/simple-3.0.tar.gz
new file mode 100644
index 00000000000..055dd525312
Binary files /dev/null and b/tests/packages/simple-3.0.tar.gz differ
diff --git a/tests/packages2/duplicate-1.0.tar.gz b/tests/packages2/duplicate-1.0.tar.gz
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/tests/test_basic.py b/tests/test_basic.py
index a49824abfb4..d5a054b14fb 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -5,7 +5,6 @@
import sys
from os.path import abspath, join, curdir, pardir
-from nose import SkipTest
from nose.tools import assert_raises
from mock import patch
@@ -492,6 +491,18 @@ def test_install_package_with_target():
assert Path('scratch')/'target'/'initools' in result.files_created, str(result)
+def test_install_package_with_root():
+ """
+ Test installing a package using pip install --root
+ """
+ env = reset_env()
+ root_dir = env.scratch_path/'root'
+ result = run_pip('install', '--root', root_dir, '--install-option=--home=',
+ '--install-option=--install-lib=/lib/python', "initools==0.1")
+
+ assert Path('scratch')/'root'/'lib'/'python'/'initools' in result.files_created, str(result)
+
+
def test_find_command_folder_in_path():
"""
If a folder named e.g. 'git' is in PATH, and find_command is looking for
diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py
index 15a0508333b..c57db91ee3d 100644
--- a/tests/test_cleanup.py
+++ b/tests/test_cleanup.py
@@ -17,6 +17,7 @@ def test_cleanup_after_install_from_pypi():
src = env.scratch_path/"src"
assert not exists(build), "build/ dir still exists: %s" % build
assert not exists(src), "unexpected src/ dir exists: %s" % src
+ env.assert_no_temp()
def test_cleanup_after_install_editable_from_hg():
@@ -34,6 +35,7 @@ def test_cleanup_after_install_editable_from_hg():
src = env.venv_path/'src'
assert not exists(build), "build/ dir still exists: %s" % build
assert exists(src), "expected src/ dir doesn't exist: %s" % src
+ env.assert_no_temp()
def test_cleanup_after_install_from_local_directory():
@@ -48,6 +50,7 @@ def test_cleanup_after_install_from_local_directory():
src = env.venv_path/'src'
assert not exists(build), "unexpected build/ dir exists: %s" % build
assert not exists(src), "unexpected src/ dir exist: %s" % src
+ env.assert_no_temp()
def test_cleanup_after_create_bundle():
@@ -79,6 +82,7 @@ def test_cleanup_after_create_bundle():
src_bundle = env.scratch_path/"src-bundle"
assert not exists(build_bundle), "build-bundle/ dir still exists: %s" % build_bundle
assert not exists(src_bundle), "src-bundle/ dir still exists: %s" % src_bundle
+ env.assert_no_temp()
# Make sure previously created src/ from editable still exists
assert exists(src), "expected src dir doesn't exist: %s" % src
@@ -96,6 +100,25 @@ def test_no_install_and_download_should_not_leave_build_dir():
assert not os.path.exists(env.venv_path/'/build'), "build/ dir should be deleted"
+def test_cleanup_req_satisifed_no_name():
+ """
+ Test cleanup when req is already satisfied, and req has no 'name'
+ """
+ #this test confirms Issue #420 is fixed
+ #reqs with no 'name' that were already satisfied were leaving behind tmp build dirs
+ #2 examples of reqs that would do this
+ # 1) https://bitbucket.org/ianb/initools/get/tip.zip
+ # 2) parent-0.1.tar.gz
+
+ dist = abspath(join(here, 'packages', 'parent-0.1.tar.gz'))
+ env = reset_env()
+ result = run_pip('install', dist)
+ result = run_pip('install', dist)
+ build = env.venv_path/'build'
+ assert not exists(build), "unexpected build/ dir exists: %s" % build
+ env.assert_no_temp()
+
+
def test_download_should_not_delete_existing_build_dir():
"""
It should not delete build/ if existing before run the command
diff --git a/tests/test_completion.py b/tests/test_completion.py
index 9381cf11d1b..9a8a451f0ab 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -45,7 +45,7 @@ def test_completion_for_unknown_shell():
Test getting completion for an unknown shell
"""
reset_env()
- error_msg = 'error: no such option: --myfooshell'
+ error_msg = 'no such option: --myfooshell'
result = run_pip('completion', '--myfooshell', expect_error=True)
assert error_msg in result.stderr, 'tests for an unknown shell failed'
diff --git a/tests/test_download.py b/tests/test_download.py
index 829d4495e61..79ef96a0e65 100644
--- a/tests/test_download.py
+++ b/tests/test_download.py
@@ -39,3 +39,32 @@ def test_download_should_download_dependencies():
openid_tarball_prefix = str(Path('scratch')/ 'python-openid-')
assert any(path.startswith(openid_tarball_prefix) for path in result.files_created)
assert env.site_packages/ 'openid' not in result.files_created
+
+
+def test_download_should_skip_existing_files():
+ """
+ It should not download files already existing in the scratch dir
+ """
+ env = reset_env()
+
+ write_file('test-req.txt', textwrap.dedent("""
+ INITools==0.1
+ """))
+
+ result = run_pip('install', '-r', env.scratch_path/ 'test-req.txt', '-d', '.', expect_error=True)
+ assert Path('scratch')/ 'INITools-0.1.tar.gz' in result.files_created
+ assert env.site_packages/ 'initools' not in result.files_created
+
+ # adding second package to test-req.txt
+ write_file('test-req.txt', textwrap.dedent("""
+ INITools==0.1
+ python-openid==2.2.5
+ """))
+
+ # only the second package should be downloaded
+ result = run_pip('install', '-r', env.scratch_path/ 'test-req.txt', '-d', '.', expect_error=True)
+ openid_tarball_prefix = str(Path('scratch')/ 'python-openid-')
+ assert any(path.startswith(openid_tarball_prefix) for path in result.files_created)
+ assert Path('scratch')/ 'INITools-0.1.tar.gz' not in result.files_created
+ assert env.site_packages/ 'initools' not in result.files_created
+ assert env.site_packages/ 'openid' not in result.files_created
diff --git a/tests/test_file_scheme_index.py b/tests/test_file_scheme_index.py
deleted file mode 100644
index 38f6654b924..00000000000
--- a/tests/test_file_scheme_index.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from pip.backwardcompat import urllib
-from tests.test_pip import here, reset_env, run_pip, pyversion
-from tests.path import Path
-
-index_url = 'file://' + urllib.quote(str(Path(here).abspath/'in dex').replace('\\', '/'))
-
-
-def test_install():
- """
- Test installing from a local index.
-
- """
- env = reset_env()
- result = run_pip('install', '-vvv', '--index-url', index_url, 'FSPkg', expect_error=False)
- assert (env.site_packages/'fspkg') in result.files_created, str(result.stdout)
- assert (env.site_packages/'FSPkg-0.1dev-py%s.egg-info' % pyversion) in result.files_created, str(result)
diff --git a/tests/test_find_links.py b/tests/test_find_links.py
new file mode 100644
index 00000000000..b9f3cde559f
--- /dev/null
+++ b/tests/test_find_links.py
@@ -0,0 +1,38 @@
+import textwrap
+
+from tests.test_pip import reset_env, run_pip, pyversion, here, write_file
+
+
+def test_find_links_relative_path():
+ """Test find-links as a relative path."""
+ e = reset_env()
+ result = run_pip(
+ 'install',
+ 'parent==0.1',
+ '--no-index',
+ '--find-links',
+ 'packages/',
+ cwd=here)
+ egg_info_folder = e.site_packages / 'parent-0.1-py%s.egg-info' % pyversion
+ initools_folder = e.site_packages / 'parent'
+ assert egg_info_folder in result.files_created, str(result)
+ assert initools_folder in result.files_created, str(result)
+
+
+def test_find_links_requirements_file_relative_path():
+ """Test find-links as a relative path to a reqs file."""
+ e = reset_env()
+ write_file('test-req.txt', textwrap.dedent("""
+ --no-index
+ --find-links=../../../packages/
+ parent==0.1
+ """))
+ result = run_pip(
+ 'install',
+ '-r',
+ e.scratch_path / "test-req.txt",
+ cwd=here)
+ egg_info_folder = e.site_packages / 'parent-0.1-py%s.egg-info' % pyversion
+ initools_folder = e.site_packages / 'parent'
+ assert egg_info_folder in result.files_created, str(result)
+ assert initools_folder in result.files_created, str(result)
diff --git a/tests/test_finder.py b/tests/test_finder.py
index cdff3a9322b..415e9a9dce9 100644
--- a/tests/test_finder.py
+++ b/tests/test_finder.py
@@ -1,12 +1,15 @@
+from pkg_resources import parse_version
from pip.backwardcompat import urllib
-
from pip.req import InstallRequirement
from pip.index import PackageFinder
-
+from pip.exceptions import BestVersionAlreadyInstalled
from tests.path import Path
from tests.test_pip import here
+from nose.tools import assert_raises
+from mock import Mock
find_links = 'file://' + urllib.quote(str(Path(here).abspath/'packages').replace('\\', '/'))
+find_links2 = 'file://' + urllib.quote(str(Path(here).abspath/'packages2').replace('\\', '/'))
def test_no_mpkg():
@@ -25,3 +28,85 @@ def test_no_partial_name_match():
found = finder.find_requirement(req, False)
assert found.url.endswith("gmpy-1.15.tar.gz"), found
+
+def test_duplicates_sort_ok():
+ """Finder successfully finds one of a set of duplicates in different
+ locations"""
+ finder = PackageFinder([find_links, find_links2], [])
+ req = InstallRequirement.from_line("duplicate")
+ found = finder.find_requirement(req, False)
+
+ assert found.url.endswith("duplicate-1.0.tar.gz"), found
+
+
+def test_finder_detects_latest_find_links():
+ """Test PackageFinder detects latest using find-links"""
+ req = InstallRequirement.from_line('simple', None)
+ finder = PackageFinder([find_links], [])
+ link = finder.find_requirement(req, False)
+ assert link.url.endswith("simple-3.0.tar.gz")
+
+
+def test_finder_detects_latest_already_satisfied_find_links():
+ """Test PackageFinder detects latest already satisified using find-links"""
+ req = InstallRequirement.from_line('simple', None)
+ #the latest simple in local pkgs is 3.0
+ latest_version = "3.0"
+ satisfied_by = Mock(
+ location = "/path",
+ parsed_version = parse_version(latest_version),
+ version = latest_version
+ )
+ req.satisfied_by = satisfied_by
+ finder = PackageFinder([find_links], [])
+ assert_raises(BestVersionAlreadyInstalled, finder.find_requirement, req, True)
+
+
+def test_finder_detects_latest_already_satisfied_pypi_links():
+ """Test PackageFinder detects latest already satisified using pypi links"""
+ req = InstallRequirement.from_line('initools', None)
+ #the latest initools on pypi is 0.3.1
+ latest_version = "0.3.1"
+ satisfied_by = Mock(
+ location = "/path",
+ parsed_version = parse_version(latest_version),
+ version = latest_version
+ )
+ req.satisfied_by = satisfied_by
+ finder = PackageFinder([], ["http://pypi.python.org/simple"])
+ assert_raises(BestVersionAlreadyInstalled, finder.find_requirement, req, True)
+
+
+def test_finder_priority_file_over_page():
+ """Test PackageFinder prefers file links over equivalent page links"""
+ req = InstallRequirement.from_line('gmpy==1.15', None)
+ finder = PackageFinder([find_links], ["http://pypi.python.org/simple"])
+ link = finder.find_requirement(req, False)
+ assert link.url.startswith("file://")
+
+
+def test_finder_priority_page_over_deplink():
+ """Test PackageFinder prefers page links over equivalent dependency links"""
+ req = InstallRequirement.from_line('gmpy==1.15', None)
+ finder = PackageFinder([], ["http://pypi.python.org/simple"])
+ finder.add_dependency_links(['http://c.pypi.python.org/simple/gmpy/'])
+ link = finder.find_requirement(req, False)
+ assert link.url.startswith("http://pypi")
+
+
+def test_finder_priority_nonegg_over_eggfragments():
+ """Test PackageFinder prefers non-egg links over "#egg=" links"""
+ req = InstallRequirement.from_line('bar==1.0', None)
+ links = ['http://foo/bar.py#egg=bar-1.0', 'http://foo/bar-1.0.tar.gz']
+
+ finder = PackageFinder(links, [])
+ link = finder.find_requirement(req, False)
+ assert link.url.endswith('tar.gz')
+
+ links.reverse()
+ finder = PackageFinder(links, [])
+ link = finder.find_requirement(req, False)
+ assert link.url.endswith('tar.gz')
+
+
+
diff --git a/tests/test_freeze.py b/tests/test_freeze.py
index 4412c143540..7e2a8b3eae5 100644
--- a/tests/test_freeze.py
+++ b/tests/test_freeze.py
@@ -91,7 +91,7 @@ def test_freeze_git_clone():
expected = textwrap.dedent("""\
Script result: ...pip freeze
-- stdout: --------------------
- -e %s@...#egg=pip_test_package-...
+ ...-e %s@...#egg=pip_test_package-...
...""" % local_checkout('git+http://github.com/pypa/pip-test-package.git'))
_check_output(result, expected)
@@ -101,7 +101,7 @@ def test_freeze_git_clone():
expected = textwrap.dedent("""\
Script result: pip freeze -f %(repo)s#egg=pip_test_package
-- stdout: --------------------
- -f %(repo)s#egg=pip_test_package
+ -f %(repo)s#egg=pip_test_package...
-e %(repo)s@...#egg=pip_test_package-dev
...""" % {'repo': local_checkout('git+http://github.com/pypa/pip-test-package.git')})
_check_output(result, expected)
@@ -124,7 +124,7 @@ def test_freeze_mercurial_clone():
expected = textwrap.dedent("""\
Script result: ...pip freeze
-- stdout: --------------------
- -e %s@...#egg=django_authority-...
+ ...-e %s@...#egg=django_authority-...
...""" % local_checkout('hg+http://bitbucket.org/jezdez/django-authority'))
_check_output(result, expected)
@@ -135,7 +135,7 @@ def test_freeze_mercurial_clone():
Script result: ...pip freeze -f %(repo)s#egg=django_authority
-- stdout: --------------------
-f %(repo)s#egg=django_authority
- -e %(repo)s@...#egg=django_authority-dev
+ ...-e %(repo)s@...#egg=django_authority-dev
...""" % {'repo': local_checkout('hg+http://bitbucket.org/jezdez/django-authority')})
_check_output(result, expected)
@@ -156,7 +156,7 @@ def test_freeze_bazaar_clone():
expected = textwrap.dedent("""\
Script result: ...pip freeze
-- stdout: --------------------
- -e %s@...#egg=django_wikiapp-...
+ ...-e %s@...#egg=django_wikiapp-...
...""" % local_checkout('bzr+http://bazaar.launchpad.net/%7Edjango-wikiapp/django-wikiapp/release-0.1'))
_check_output(result, expected)
@@ -168,7 +168,7 @@ def test_freeze_bazaar_clone():
Script result: ...pip freeze -f %(repo)s/#egg=django-wikiapp
-- stdout: --------------------
-f %(repo)s/#egg=django-wikiapp
- -e %(repo)s@...#egg=django_wikiapp-...
+ ...-e %(repo)s@...#egg=django_wikiapp-...
...""" % {'repo':
local_checkout('bzr+http://bazaar.launchpad.net/%7Edjango-wikiapp/django-wikiapp/release-0.1')})
_check_output(result, expected)
diff --git a/tests/test_hashes.py b/tests/test_hashes.py
index 1e738f43b1d..79c4f164992 100644
--- a/tests/test_hashes.py
+++ b/tests/test_hashes.py
@@ -13,7 +13,7 @@ def test_get_hash_from_file_md5():
download_hash = _get_hash_from_file(file_path, file_link)
- assert download_hash.name == "md5"
+ assert download_hash.digest_size == 16
assert download_hash.hexdigest() == "d41d8cd98f00b204e9800998ecf8427e"
@@ -23,7 +23,7 @@ def test_get_hash_from_file_sha1():
download_hash = _get_hash_from_file(file_path, file_link)
- assert download_hash.name == "sha1"
+ assert download_hash.digest_size == 20
assert download_hash.hexdigest() == "da39a3ee5e6b4b0d3255bfef95601890afd80709"
@@ -33,7 +33,7 @@ def test_get_hash_from_file_sha224():
download_hash = _get_hash_from_file(file_path, file_link)
- assert download_hash.name == "sha224"
+ assert download_hash.digest_size == 28
assert download_hash.hexdigest() == "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"
@@ -43,7 +43,7 @@ def test_get_hash_from_file_sha384():
download_hash = _get_hash_from_file(file_path, file_link)
- assert download_hash.name == "sha384"
+ assert download_hash.digest_size == 48
assert download_hash.hexdigest() == "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
@@ -53,7 +53,7 @@ def test_get_hash_from_file_sha256():
download_hash = _get_hash_from_file(file_path, file_link)
- assert download_hash.name == "sha256"
+ assert download_hash.digest_size == 32
assert download_hash.hexdigest() == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
@@ -63,7 +63,7 @@ def test_get_hash_from_file_sha512():
download_hash = _get_hash_from_file(file_path, file_link)
- assert download_hash.name == "sha512"
+ assert download_hash.digest_size == 64
assert download_hash.hexdigest() == "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
diff --git a/tests/test_index.py b/tests/test_index.py
index 6f9d216dc05..5c78fd7bf77 100644
--- a/tests/test_index.py
+++ b/tests/test_index.py
@@ -1,4 +1,11 @@
-from pip.index import package_to_requirement, HTMLPage
+import os
+from pip.backwardcompat import urllib
+from tests.path import Path
+from pip.index import package_to_requirement, HTMLPage, get_mirrors, DEFAULT_MIRROR_HOSTNAME
+from pip.index import PackageFinder, Link, InfLink
+from tests.test_pip import reset_env, run_pip, pyversion, here
+from string import ascii_lowercase
+from mock import patch
def test_package_name_should_be_converted_to_requirement():
@@ -26,3 +33,99 @@ def test_html_page_should_be_able_to_scrap_rel_links():
assert len(links) == 1
assert links[0].url == 'http://supervisord.org/'
+
+def test_html_page_should_be_able_to_filter_links_by_rel():
+ """
+ Test selecting links by the rel attribute
+ """
+ page = HTMLPage("""
+ Some page
+ Download URL
+ Homepage
+ """, "archive")
+
+ links = list(page.rel_links())
+ urls = [l.url for l in links]
+ hlinks = list(page.rel_links(('homepage',)))
+ dlinks = list(page.rel_links(('download',)))
+ assert len(links) == 2
+ assert 'http://example.com/archive-1.2.3.tar.gz' in urls
+ assert 'http://example.com/home.html' in urls
+ assert len(hlinks) == 1
+ assert hlinks[0].url == 'http://example.com/home.html'
+ assert len(dlinks) == 1
+ assert dlinks[0].url == 'http://example.com/archive-1.2.3.tar.gz'
+
+
+@patch('socket.gethostbyname_ex')
+def test_get_mirrors(mock_gethostbyname_ex):
+ # Test when the expected result comes back
+ # from socket.gethostbyname_ex
+ mock_gethostbyname_ex.return_value = ('g.pypi.python.org', [DEFAULT_MIRROR_HOSTNAME], ['129.21.171.98'])
+ mirrors = get_mirrors()
+ # Expect [a-g].pypi.python.org, since last mirror
+ # is returned as g.pypi.python.org
+ assert len(mirrors) == 7
+ for c in "abcdefg":
+ assert c + ".pypi.python.org" in mirrors
+
+@patch('socket.gethostbyname_ex')
+def test_get_mirrors_no_cname(mock_gethostbyname_ex):
+ # Test when the UNexpected result comes back
+ # from socket.gethostbyname_ex
+ # (seeing this in Japan and was resulting in 216k
+ # invalid mirrors and a hot CPU)
+ mock_gethostbyname_ex.return_value = (DEFAULT_MIRROR_HOSTNAME, [DEFAULT_MIRROR_HOSTNAME], ['129.21.171.98'])
+ mirrors = get_mirrors()
+ # Falls back to [a-z].pypi.python.org
+ assert len(mirrors) == 26
+ for c in ascii_lowercase:
+ assert c + ".pypi.python.org" in mirrors
+
+
+def test_sort_locations_file_find_link():
+ """
+ Test that a file:// find-link dir gets listdir run
+ """
+ find_links_url = 'file://' + os.path.join(here, 'packages')
+ find_links = [find_links_url]
+ finder = PackageFinder(find_links, [])
+ files, urls = finder._sort_locations(find_links)
+ assert files and not urls, "files and not urls should have been found at find-links url: %s" % find_links_url
+
+
+def test_sort_locations_file_not_find_link():
+ """
+ Test that a file:// url dir that's not a find-link, doesn't get a listdir run
+ """
+ index_url = 'file://' + os.path.join(here, 'indexes', 'empty_with_pkg')
+ finder = PackageFinder([], [])
+ files, urls = finder._sort_locations([index_url])
+ assert urls and not files, "urls, but not files should have been found"
+
+
+def test_install_from_file_index_hash_link():
+ """
+ Test that a pkg can be installed from a file:// index using a link with a hash
+ """
+ env = reset_env()
+ index_url = 'file://' + os.path.join(here, 'indexes', 'simple')
+ result = run_pip('install', '-i', index_url, 'simple==1.0')
+ egg_info_folder = env.site_packages / 'simple-1.0-py%s.egg-info' % pyversion
+ assert egg_info_folder in result.files_created, str(result)
+
+
+def test_file_index_url_quoting():
+ """
+ Test url quoting of file index url with a space
+ """
+ index_url = 'file://' + urllib.quote(str(Path(here).abspath/'indexes'/'in dex').replace('\\', '/'))
+ env = reset_env()
+ result = run_pip('install', '-vvv', '--index-url', index_url, 'simple', expect_error=False)
+ assert (env.site_packages/'simple') in result.files_created, str(result.stdout)
+ assert (env.site_packages/'simple-1.0-py%s.egg-info' % pyversion) in result.files_created, str(result)
+
+
+def test_inflink_greater():
+ """Test InfLink compares greater."""
+ assert InfLink > Link(object())
diff --git a/tests/test_pip.py b/tests/test_pip.py
index e8cc6a03c8e..45985af3a5d 100644
--- a/tests/test_pip.py
+++ b/tests/test_pip.py
@@ -314,12 +314,18 @@ def __init__(self, environ=None, use_distribute=None, sitecustomize=None):
assert self.venv_path == virtualenv_paths[0] # sanity check
for id, path in zip(('venv', 'lib', 'include', 'bin'), virtualenv_paths):
+ #fix for virtualenv issue #306
+ if hasattr(sys, "pypy_version_info") and id == 'lib':
+ path = os.path.join(self.venv_path, 'lib-python', pyversion)
setattr(self, id+'_path', Path(path))
setattr(self, id, relpath(self.root_path, path))
assert self.venv == TestPipEnvironment.venv # sanity check
- self.site_packages = self.lib/'site-packages'
+ if hasattr(sys, "pypy_version_info"):
+ self.site_packages = self.venv/'site-packages'
+ else:
+ self.site_packages = self.lib/'site-packages'
self.user_base_path = self.venv_path/'user'
self.user_site_path = self.venv_path/'user'/site_packages_suffix
@@ -362,6 +368,10 @@ def __init__(self, environ=None, use_distribute=None, sitecustomize=None):
if sitecustomize:
self._add_to_sitecustomize(sitecustomize)
+ # Ensure that $TMPDIR exists (because we use start_clear=False, it's not created for us)
+ if self.temp_path and not os.path.exists(self.temp_path):
+ os.makedirs(self.temp_path)
+
def _ignore_file(self, fn):
if fn.endswith('__pycache__') or fn.endswith(".pyc"):
result = True
@@ -450,12 +460,18 @@ def __init__(self, environ=None, sitecustomize=None):
virtualenv_paths = virtualenv.path_locations(self.venv_path)
for id, path in zip(('venv', 'lib', 'include', 'bin'), virtualenv_paths):
+ #fix for virtualenv issue #306
+ if hasattr(sys, "pypy_version_info") and id == 'lib':
+ path = os.path.join(self.venv_path, 'lib-python', pyversion)
setattr(self, id+'_path', Path(path))
setattr(self, id, relpath(self.root_path, path))
assert self.venv == TestPipEnvironment.venv # sanity check
- self.site_packages = self.lib/'site-packages'
+ if hasattr(sys, "pypy_version_info"):
+ self.site_packages = self.venv/'site-packages'
+ else:
+ self.site_packages = self.lib/'site-packages'
self.user_base_path = self.venv_path/'user'
self.user_site_path = self.venv_path/'user'/'lib'/self.lib.name/'site-packages'
@@ -516,6 +532,10 @@ def __init__(self, environ=None, sitecustomize=None):
assert self.root_path.exists
+ # Ensure that $TMPDIR exists (because we use start_clear=False, it's not created for us)
+ if self.temp_path and not os.path.exists(self.temp_path):
+ os.makedirs(self.temp_path)
+
def __del__(self):
pass # shutil.rmtree(str(self.root_path), ignore_errors=True)
@@ -669,5 +689,5 @@ def main():
if __name__ == '__main__':
- sys.stderr.write("Run pip's tests using nosetests. Requires virtualenv, ScriptTest, and nose.\n")
+ sys.stderr.write("Run pip's tests using nosetests. Requires virtualenv, ScriptTest, mock, and nose.\n")
sys.exit(1)
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index a50feeb26d5..a21478ac6e3 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -80,20 +80,23 @@ def test_multiple_requirements_files():
def test_respect_order_in_requirements_file():
env = reset_env()
write_file('frameworks-req.txt', textwrap.dedent("""\
- bidict
- ordereddict
- initools
+ parent
+ child
+ simple
"""))
- result = run_pip('install', '-r', env.scratch_path / 'frameworks-req.txt')
+
+ find_links = 'file://' + os.path.join(here, 'packages')
+ result = run_pip('install', '--no-index', '-f', find_links, '-r', env.scratch_path / 'frameworks-req.txt')
+
downloaded = [line for line in result.stdout.split('\n')
if 'Downloading/unpacking' in line]
- assert 'bidict' in downloaded[0], 'First download should ' \
- 'be "bidict" but was "%s"' % downloaded[0]
- assert 'ordereddict' in downloaded[1], 'Second download should ' \
- 'be "ordereddict" but was "%s"' % downloaded[1]
- assert 'initools' in downloaded[2], 'Third download should ' \
- 'be "initools" but was "%s"' % downloaded[2]
+ assert 'parent' in downloaded[0], 'First download should ' \
+ 'be "parent" but was "%s"' % downloaded[0]
+ assert 'child' in downloaded[1], 'Second download should ' \
+ 'be "child" but was "%s"' % downloaded[1]
+ assert 'simple' in downloaded[2], 'Third download should ' \
+ 'be "simple" but was "%s"' % downloaded[2]
def test_requirements_data_structure_keeps_order():
@@ -171,7 +174,7 @@ def test_parse_editable_local_extras(isdir_mock, exists_mock, getcwd_mock, normc
normcase_mock.return_value = "/some/path/foo"
assert_equal(
parse_editable('foo[bar,baz]', 'git'),
- (None, 'file://' + os.path.join("/some/path", 'foo'), ('bar', 'baz'))
+ (None, 'file:///some/path/foo', ('bar', 'baz'))
)
def test_install_local_editable_with_extras():
@@ -180,4 +183,27 @@ def test_install_local_editable_with_extras():
res = run_pip('install', '-e', to_install + '[bar]', expect_error=False)
assert env.site_packages/'easy-install.pth' in res.files_updated
assert env.site_packages/'LocalExtras.egg-link' in res.files_created
- assert env.site_packages/'fspkg' in res.files_created
+ assert env.site_packages/'simple' in res.files_created
+
+
+def test_url_req_case_mismatch():
+ """
+ tar ball url requirements (with no egg fragment), that happen to have upper case project names,
+ should be considered equal to later requirements that reference the project name using lower case.
+
+ tests/packages contains Upper-1.0.tar.gz and Upper-2.0.tar.gz
+ 'requiresupper' has install_requires = ['upper']
+ """
+ env = reset_env()
+ find_links = 'file://' + os.path.join(here, 'packages')
+ Upper = os.path.join(find_links, 'Upper-1.0.tar.gz')
+ result = run_pip('install', '--no-index', '-f', find_links, Upper, 'requiresupper')
+
+ #only Upper-1.0.tar.gz should get installed.
+ egg_folder = env.site_packages / 'Upper-1.0-py%s.egg-info' % pyversion
+ assert egg_folder in result.files_created, str(result)
+ egg_folder = env.site_packages / 'Upper-2.0-py%s.egg-info' % pyversion
+ assert egg_folder not in result.files_created, str(result)
+
+
+
diff --git a/tests/test_show.py b/tests/test_show.py
new file mode 100644
index 00000000000..774fe9a7b79
--- /dev/null
+++ b/tests/test_show.py
@@ -0,0 +1,88 @@
+import re
+from pip import __version__
+from pip.commands.show import search_packages_info
+from tests.test_pip import reset_env, run_pip
+
+
+def test_show():
+ """
+ Test end to end test for show command.
+
+ """
+ reset_env()
+ result = run_pip('show', 'pip')
+ lines = result.stdout.split('\n')
+ assert len(lines) == 6
+ assert lines[0] == '---', lines[0]
+ assert lines[1] == 'Name: pip', lines[1]
+ assert lines[2] == 'Version: %s' % __version__, lines[2]
+ assert lines[3].startswith('Location: '), lines[3]
+ assert lines[4] == 'Requires: '
+
+
+def test_show_with_files_not_found():
+ """
+ Test for show command with installed files listing enabled and
+ installed-files.txt not found.
+
+ """
+ reset_env()
+ result = run_pip('show', '-f', 'pip')
+ lines = result.stdout.split('\n')
+ assert len(lines) == 8
+ assert lines[0] == '---', lines[0]
+ assert lines[1] == 'Name: pip', lines[1]
+ assert lines[2] == 'Version: %s' % __version__, lines[2]
+ assert lines[3].startswith('Location: '), lines[3]
+ assert lines[4] == 'Requires: '
+ assert lines[5] == 'Files:', lines[4]
+ assert lines[6] == 'Cannot locate installed-files.txt', lines[5]
+
+
+def test_show_with_all_files():
+ """
+ Test listing all files in the show command.
+
+ """
+ reset_env()
+ result = run_pip('install', 'initools==0.2')
+ result = run_pip('show', '--files', 'initools')
+ assert re.search(r"Files:\n( .+\n)+", result.stdout)
+
+
+def test_missing_argument():
+ """
+ Test show command with no arguments.
+
+ """
+ reset_env()
+ result = run_pip('show')
+ assert 'ERROR: Please provide a project name or names.' in result.stdout
+
+
+def test_find_package_not_found():
+ """
+ Test trying to get info about a nonexistent package.
+
+ """
+ result = search_packages_info(['abcd3'])
+ assert len(list(result)) == 0
+
+
+def test_search_any_case():
+ """
+ Search for a package in any case.
+
+ """
+ result = list(search_packages_info(['PIP']))
+ assert len(result) == 1
+ assert 'pip' == result[0]['name']
+
+
+def test_more_than_one_package():
+ """
+ Search for more than one package.
+
+ """
+ result = list(search_packages_info(['Pip', 'Nose', 'Virtualenv']))
+ assert len(result) == 3
diff --git a/tests/test_test.py b/tests/test_test.py
index a5293957526..498652d959f 100644
--- a/tests/test_test.py
+++ b/tests/test_test.py
@@ -66,3 +66,24 @@ def test_sitecustomize_not_growing_in_fast_environment():
size2 = os.stat(sc2).st_size
assert size1==size2, "size before, %d != size after, %d" %(size1, size2)
+
+def test_tmp_dir_exists_in_env():
+ """
+ Test that $TMPDIR == env.temp_path and path exists, and env.assert_no_temp() passes
+ """
+ #need these tests to ensure the assert_no_temp feature of scripttest is working
+ env = reset_env(use_distribute=True)
+ env.assert_no_temp() #this fails if env.tmp_path doesn't exist
+ assert env.environ['TMPDIR'] == env.temp_path
+ assert isdir(env.temp_path)
+
+
+def test_tmp_dir_exists_in_fast_env():
+ """
+ Test that $TMPDIR == env.temp_path and path exists and env.assert_no_temp() passes (in fast env)
+ """
+ #need these tests to ensure the assert_no_temp feature of scripttest is working
+ env = reset_env()
+ env.assert_no_temp() #this fails if env.tmp_path doesn't exist
+ assert env.environ['TMPDIR'] == env.temp_path
+ assert isdir(env.temp_path)
diff --git a/tests/test_unicode.py b/tests/test_unicode.py
index d9196e7505b..eb926494ec4 100644
--- a/tests/test_unicode.py
+++ b/tests/test_unicode.py
@@ -20,6 +20,6 @@ def test_install_package_that_emits_unicode():
env = reset_env()
to_install = os.path.abspath(os.path.join(here, 'packages', 'BrokenEmitsUTF8'))
- result = run_pip('install', to_install, expect_error=True)
- assert '__main__.FakeError: this package designed to fail on install' in result.stdout
+ result = run_pip('install', to_install, expect_error=True, expect_temp=True, quiet=True)
+ assert 'FakeError: this package designed to fail on install' in result.stdout
assert 'UnicodeDecodeError' not in result.stdout
diff --git a/tests/test_uninstall.py b/tests/test_uninstall.py
index 3a0fac92cfa..359d59688b1 100644
--- a/tests/test_uninstall.py
+++ b/tests/test_uninstall.py
@@ -1,9 +1,10 @@
+from __future__ import with_statement
+
import textwrap
import sys
-from os.path import join, abspath
+from os.path import join, abspath, normpath
from tempfile import mkdtemp
-from mock import Mock
-from nose.tools import assert_raises
+from mock import patch
from tests.test_pip import here, reset_env, run_pip, assert_all_changes, write_file, pyversion
from tests.local_repos import local_repo, local_checkout
@@ -18,6 +19,8 @@ def test_simple_uninstall():
env = reset_env()
result = run_pip('install', 'INITools==0.2')
assert join(env.site_packages, 'initools') in result.files_created, sorted(result.files_created.keys())
+ #the import forces the generation of __pycache__ if the version of python supports it
+ env.run('python', '-c', "import initools")
result2 = run_pip('uninstall', 'INITools', '-y')
assert_all_changes(result, result2, [env.venv/'build', 'cache'])
@@ -36,6 +39,19 @@ def test_uninstall_with_scripts():
assert_all_changes(result, result2, [env.venv/'build', 'cache'])
+def test_uninstall_easy_install_after_import():
+ """
+ Uninstall an easy_installed package after it's been imported
+
+ """
+ env = reset_env()
+ result = env.run('easy_install', 'INITools==0.2', expect_stderr=True)
+ #the import forces the generation of __pycache__ if the version of python supports it
+ env.run('python', '-c', "import initools")
+ result2 = run_pip('uninstall', 'INITools', '-y')
+ assert_all_changes(result, result2, [env.venv/'build', 'cache'])
+
+
def test_uninstall_namespace_package():
"""
Uninstall a distribution with a namespace package without clobbering
@@ -65,10 +81,12 @@ def test_uninstall_overlapping_package():
assert join(env.site_packages, 'parent') in result1.files_created, sorted(result1.files_created.keys())
result2 = run_pip('install', child_pkg, expect_error=False)
assert join(env.site_packages, 'child') in result2.files_created, sorted(result2.files_created.keys())
- assert join(env.site_packages, 'parent/plugins/child_plugin.py') in result2.files_created, sorted(result2.files_created.keys())
+ assert normpath(join(env.site_packages, 'parent/plugins/child_plugin.py')) in result2.files_created, sorted(result2.files_created.keys())
+ #the import forces the generation of __pycache__ if the version of python supports it
+ env.run('python', '-c', "import parent.plugins.child_plugin, child")
result3 = run_pip('uninstall', '-y', 'child', expect_error=False)
assert join(env.site_packages, 'child') in result3.files_deleted, sorted(result3.files_created.keys())
- assert join(env.site_packages, 'parent/plugins/child_plugin.py') in result3.files_deleted, sorted(result3.files_deleted.keys())
+ assert normpath(join(env.site_packages, 'parent/plugins/child_plugin.py')) in result3.files_deleted, sorted(result3.files_deleted.keys())
assert join(env.site_packages, 'parent') not in result3.files_deleted, sorted(result3.files_deleted.keys())
# Additional check: uninstalling 'child' should return things to the
# previous state, without unintended side effects.
@@ -183,13 +201,41 @@ def test_uninstall_as_egg():
assert_all_changes(result, result2, [env.venv/'build', 'cache'])
-def test_uninstallpathset_no_paths():
+@patch('pip.req.logger')
+def test_uninstallpathset_no_paths(mock_logger):
"""
- Test UninstallPathSet raises installation error when there are no paths (uses mocking)
+ Test UninstallPathSet logs notification when there are no paths to uninstall
"""
from pip.req import UninstallPathSet
- from pip.exceptions import InstallationError
- mock_dist = Mock(project_name='pkg')
- uninstall_set = UninstallPathSet(mock_dist)
- assert_raises(InstallationError, uninstall_set.remove)
+ from pkg_resources import get_distribution
+ test_dist = get_distribution('pip')
+ # ensure that the distribution is "local"
+ with patch("pip.req.dist_is_local") as mock_dist_is_local:
+ mock_dist_is_local.return_value = True
+ uninstall_set = UninstallPathSet(test_dist)
+ uninstall_set.remove() #with no files added to set
+ mock_logger.notify.assert_any_call("Can't uninstall 'pip'. No files were found to uninstall.")
+
+
+@patch('pip.req.logger')
+def test_uninstallpathset_non_local(mock_logger):
+ """
+ Test UninstallPathSet logs notification and returns (with no exception) when dist is non-local
+
+ """
+ from pip.req import UninstallPathSet
+ from pkg_resources import get_distribution
+ test_dist = get_distribution('pip')
+ test_dist.location = "/NON_LOCAL"
+ # ensure that the distribution is "non-local"
+ # setting location isn't enough, due to egg-link file checking for
+ # develop-installs
+ with patch("pip.req.dist_is_local") as mock_dist_is_local:
+ mock_dist_is_local.return_value = False
+ uninstall_set = UninstallPathSet(test_dist)
+ uninstall_set.remove() #with no files added to set; which is the case when trying to remove non-local dists
+ mock_logger.notify.assert_any_call("Not uninstalling pip at /NON_LOCAL, outside environment %s" % sys.prefix)
+
+
+
diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py
index 599b317f7f4..7fee19dcb31 100644
--- a/tests/test_upgrade.py
+++ b/tests/test_upgrade.py
@@ -50,11 +50,12 @@ def test_upgrade_with_newest_already_installed():
not be reinstalled and the user should be informed.
"""
+ find_links = 'file://' + join(here, 'packages')
env = reset_env()
- run_pip('install', 'INITools')
- result = run_pip('install', '--upgrade', 'INITools')
- assert not result.files_created, 'pip install --upgrade INITools upgraded when it should not have'
- assert 'already up-to-date' in result.stdout
+ run_pip('install', '-f', find_links, '--no-index', 'simple')
+ result = run_pip('install', '--upgrade', '-f', find_links, '--no-index', 'simple')
+ assert not result.files_created, 'simple upgraded when it should not have'
+ assert 'already up-to-date' in result.stdout, result.stdout
def test_upgrade_without_unneeded_recursive_upgrades():
diff --git a/tests/test_user_site.py b/tests/test_user_site.py
index 9b455898541..e4f5c73a8f5 100644
--- a/tests/test_user_site.py
+++ b/tests/test_user_site.py
@@ -35,6 +35,10 @@ def setup(self):
# --user only works on 2.6 or higher
if sys.version_info < (2, 6):
raise SkipTest()
+ # --user option is broken in pypy
+ if hasattr(sys, "pypy_version_info"):
+ raise SkipTest()
+
def test_reset_env_system_site_packages_usersite(self):
"""
@@ -66,9 +70,6 @@ def test_install_subversion_usersite_editable_with_distribute(self):
"""
Test installing current directory ('.') into usersite after installing distribute
"""
- # FIXME distutils --user option seems to be broken in pypy
- if hasattr(sys, "pypy_version_info"):
- raise SkipTest()
env = reset_env(use_distribute=True, system_site_packages=True)
result = run_pip('install', '--user', '-e',
'%s#egg=initools-dev' %
@@ -80,9 +81,6 @@ def test_install_curdir_usersite(self):
"""
Test installing current directory ('.') into usersite
"""
- # FIXME distutils --user option seems to be broken in pypy
- if hasattr(sys, "pypy_version_info"):
- raise SkipTest()
env = reset_env(use_distribute=True, system_site_packages=True)
run_from = abspath(join(here, 'packages', 'FSPkg'))
result = run_pip('install', '--user', curdir, cwd=run_from, expect_error=False)
@@ -150,6 +148,37 @@ def test_install_user_conflict_in_globalsite(self):
assert isdir(initools_folder)
+ def test_upgrade_user_conflict_in_globalsite(self):
+ """
+ Test user install/upgrade with conflict in global site ignores site and installs to usersite
+ """
+
+ # the test framework only supports testing using virtualenvs
+ # the sys.path ordering for virtualenvs with --system-site-packages is this: virtualenv-site, user-site, global-site
+ # this test will use 2 modifications to simulate the user-site/global-site relationship
+ # 1) a monkey patch which will make it appear INITools==0.2 is not in in the virtualenv site
+ # if we don't patch this, pip will return an installation error: "Will not install to the usersite because it will lack sys.path precedence..."
+ # 2) adding usersite to PYTHONPATH, so usersite as sys.path precedence over the virtualenv site
+
+ env = reset_env(system_site_packages=True, sitecustomize=patch_dist_in_site_packages)
+ env.environ["PYTHONPATH"] = env.root_path / env.user_site
+
+ result1 = run_pip('install', 'INITools==0.2')
+ result2 = run_pip('install', '--user', '--upgrade', 'INITools')
+
+ #usersite has 0.3.1
+ egg_info_folder = env.user_site / 'INITools-0.3.1-py%s.egg-info' % pyversion
+ initools_folder = env.user_site / 'initools'
+ assert egg_info_folder in result2.files_created, str(result2)
+ assert initools_folder in result2.files_created, str(result2)
+
+ #site still has 0.2 (can't look in result1; have to check)
+ egg_info_folder = env.root_path / env.site_packages / 'INITools-0.2-py%s.egg-info' % pyversion
+ initools_folder = env.root_path / env.site_packages / 'initools'
+ assert isdir(egg_info_folder), result2.stdout
+ assert isdir(initools_folder)
+
+
def test_install_user_conflict_in_globalsite_and_usersite(self):
"""
Test user install with conflict in globalsite and usersite ignores global site and updates usersite.
diff --git a/tests/test_util.py b/tests/test_util.py
index 2907cc06714..305591b3708 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -8,6 +8,7 @@
from nose.tools import eq_
from tests.path import Path
from pip.util import egg_link_path
+from pip.util import Inf
class Tests_EgglinkPath:
@@ -138,3 +139,10 @@ def test_noegglink_in_sitepkgs_venv_global(self):
self.mock_isfile.return_value = False
eq_(egg_link_path(self.mock_dist), None)
+def test_Inf_greater():
+ """Test Inf compares greater."""
+ assert Inf > object()
+
+def test_Inf_equals_Inf():
+ """Test Inf compares greater."""
+ assert Inf == Inf
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000000..731f8a69111
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,8 @@
+[tox]
+envlist =
+ py25,py26,py27,py32,py33,pypy
+
+[testenv]
+commands =
+ python setup.py dev
+ python setup.py nosetests