summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJannis Leidel <jannis@leidel.info>2012-01-07 17:34:15 +0100
committerJannis Leidel <jannis@leidel.info>2012-01-07 17:34:15 +0100
commitefa479c50249b00493807a325f2713c592306fcb (patch)
tree5a8d417baf48943b9bff309ea030b62d3a4c27b4
parent550f6705d5b53d5b526f97b163a2e8a28966d7fe (diff)
parent6d6ead7923b80271243dc59225b5a29b87f28306 (diff)
downloadpip-feature/pep381-verification.tar.gz
Merge branch 'develop' into feature/pep381-verificationfeature/pep381-verification
Conflicts: pip/basecommand.py pip/download.py
-rw-r--r--AUTHORS.txt1
-rw-r--r--docs/news.txt2
-rw-r--r--docs/requirements.txt19
-rw-r--r--docs/usage.txt17
-rw-r--r--pip/basecommand.py25
-rw-r--r--pip/commands/help.py12
-rw-r--r--pip/commands/search.py10
-rw-r--r--pip/download.py4
-rw-r--r--pip/status_codes.py5
-rw-r--r--tests/test_help.py62
-rw-r--r--tests/test_search.py47
11 files changed, 169 insertions, 35 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 28be229b5..0739afff4 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -30,6 +30,7 @@ Oliver Tonnhofer
Olivier Girardot
Patrick Jenkins
Paul Nasrat
+Paul Oswald
Peter Waller
Rene Dudfield
Ronny Pfannschmidt
diff --git a/docs/news.txt b/docs/news.txt
index b1ab31c2a..dbd9583d3 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -41,6 +41,8 @@ develop (unreleased)
* Fixed issue #366 - pip throws IndexError when it calls `scraped_rel_links`
+* Fixed issue #22 - pip search should set and return a userful shell status code
+
1.0.2 (2011-07-16)
------------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index e8d4c1bac..326937da6 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -59,8 +59,7 @@ break MyApp. If that happens you can update the requirements file to
force an earlier version of Library, and you can do that without
having to re-release MyApp at all.
-Read the `requirements file format <http://pip.openplans.org/requirement-format.html>`_ to
-learn about other features.
+Read the `requirements file format`_ to learn about other features.
Freezing Requirements
=====================
@@ -86,8 +85,8 @@ sort of template for the new file. So if you do::
it will keep the packages listed in ``devel-req.txt`` in order and preserve
comments.
-The requirements file format
-============================
+The _`requirements file format`
+===============================
The requirements file is a way to get pip to install specific packages
to make up an *environment*. This document describes that format. To
@@ -99,9 +98,15 @@ installed. For example::
MyPackage==3.0
-tells pip to install the 3.0 version of MyPackage.
+tells pip to install the 3.0 version of MyPackage.
-You can also install a package in an "editable" form. This puts the
+You can also request `extras`_ in the requirements file::
+
+ MyPackage==3.0 [PDF]
+
+.. _extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
+
+Packages may also be installed in an "editable" form. This puts the
source code into ``src/distname`` (making the name lower case) and
runs ``python setup.py develop`` on the package. To indicate
editable, use ``-e``, like::
@@ -164,7 +169,7 @@ Pip currently supports cloning over ``git``, ``git+http`` and ``git+ssh``::
-e git+http://git.myproject.org/MyProject/#egg=MyProject
-e git+ssh://git@myproject.org/MyProject/#egg=MyProject
-Passing branch names, a commit hash or a tag name is also possible::
+Passing branch names, a commit hash or a tag name is also possible::
-e git://git.myproject.org/MyProject.git@master#egg=MyProject
-e git://git.myproject.org/MyProject.git@v1.0#egg=MyProject
diff --git a/docs/usage.txt b/docs/usage.txt
index 24582dd57..991d0d54e 100644
--- a/docs/usage.txt
+++ b/docs/usage.txt
@@ -12,9 +12,10 @@ The simplest way to install a package is by specifying its name::
`SomePackage` is downloaded from :term:`PyPI`, along with its
dependencies, and installed.
-If `SomePackage` is already installed, and you need a newer version, use
-``pip install --upgrade SomePackage``. You can also request a specific
-version: ``pip install SomePackage==1.0.4``.
+If `SomePackage` is already installed, and you need a newer version, use ``pip
+install --upgrade SomePackage``. You can also request a specific version (``pip
+install SomePackage==1.0.4``) and specify `setuptools extras`_ (``pip install
+SomePackage[PDF]``).
You can also install from a particular source distribution file, either
local or remote::
@@ -22,6 +23,9 @@ local or remote::
$ pip install ./downloads/SomePackage-1.0.4.tar.gz
$ pip install http://my.package.repo/SomePackage-1.0.4.zip
+.. _setuptools extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
+
+
Edit mode
*********
@@ -35,6 +39,7 @@ package::
.. _normally: http://docs.python.org/install/index.html#how-installation-works
+
Version control systems
***********************
@@ -51,6 +56,10 @@ folder by appending a hash to the repository URL::
$ pip install -e git+https://github.com/lakshmivyas/hyde.git#egg=hyde
+Note that only basic checking-out of a repo is supported; pip will not
+handle advanced VCS-specific features such as submodules or subrepos.
+
+
Alternate package repositories
******************************
@@ -114,6 +123,7 @@ from conflicting versions aren't left hanging around to cause trouble. The
old version of the package is automatically restored if the new version
fails to download or install.
+
Searching for packages
----------------------
@@ -126,6 +136,7 @@ The query will be used to search the names and summaries of all
packages. With the ``--index`` option you can search in a different
repository.
+
Bundles
-------
diff --git a/pip/basecommand.py b/pip/basecommand.py
index 099bf2f70..c80790629 100644
--- a/pip/basecommand.py
+++ b/pip/basecommand.py
@@ -13,8 +13,11 @@ from pip.baseparser import (parser, ConfigOptionParser,
from pip.download import urlopen
from pip.exceptions import (BadCommand, InstallationError,
UninstallationError, CommandError)
+from pip.backwardcompat import StringIO, walk_packages
from pip.log import logger
from pip.locations import serverkey_file
+from pip.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND
+
__all__ = ['command_dict', 'Command', 'load_all_commands',
'load_command', 'command_names']
@@ -24,7 +27,6 @@ command_dict = {}
# for backwards compatibiliy
get_proxy = urlopen.get_proxy
-
class Command(object):
name = None
usage = None
@@ -100,7 +102,7 @@ class Command(object):
if not os.environ.get('VIRTUAL_ENV'):
logger.fatal('Could not find an activated '
'virtualenv (required).')
- sys.exit(3)
+ sys.exit(VIRTUALENV_NOT_FOUND)
if not os.path.exists(serverkey_file) or options.refresh_serverkey:
self.refresh_serverkey()
@@ -115,37 +117,40 @@ class Command(object):
urlopen.setup(proxystr=options.proxy, prompting=not options.no_input)
- exit = 0
+ exit = SUCCESS
store_log = False
try:
- self.run(options, args)
+ status = self.run(options, args)
+ # FIXME: all commands should return an exit status
+ # and when it is done, isinstance is not needed anymore
+ if isinstance(status, int):
+ exit = status
except (InstallationError, UninstallationError):
e = sys.exc_info()[1]
logger.fatal(str(e))
logger.info('Exception information:\n%s' % format_exc())
store_log = True
- exit = 1
+ exit = ERROR
except BadCommand:
e = sys.exc_info()[1]
logger.fatal(str(e))
logger.info('Exception information:\n%s' % format_exc())
store_log = True
- exit = 1
+ exit = ERROR
except CommandError:
e = sys.exc_info()[1]
logger.fatal('ERROR: %s' % e)
logger.info('Exception information:\n%s' % format_exc())
- exit = 1
+ exit = ERROR
except KeyboardInterrupt:
logger.fatal('Operation cancelled by user')
logger.info('Exception information:\n%s' % format_exc())
store_log = True
- exit = 1
+ exit = ERROR
except:
logger.fatal('Exception:\n%s' % format_exc())
store_log = True
- exit = 2
-
+ exit = UNKNOWN_ERROR
if log_fp is not None:
log_fp.close()
if store_log:
diff --git a/pip/commands/help.py b/pip/commands/help.py
index b5b31e702..4d504c521 100644
--- a/pip/commands/help.py
+++ b/pip/commands/help.py
@@ -1,5 +1,7 @@
-from pip.basecommand import Command, command_dict, load_all_commands
-from pip.exceptions import InstallationError
+from pip.basecommand import (Command, command_dict,
+ load_all_commands, SUCCESS,
+ ERROR)
+from pip.exceptions import CommandError
from pip.baseparser import parser
@@ -14,10 +16,10 @@ class HelpCommand(Command):
## FIXME: handle errors better here
command = args[0]
if command not in command_dict:
- raise InstallationError('No command with the name: %s' % command)
+ raise CommandError('No command with the name: %s' % command)
command = command_dict[command]
command.parser.print_help()
- return
+ return SUCCESS
parser.print_help()
print('\nCommands available:')
commands = list(set(command_dict.values()))
@@ -26,6 +28,6 @@ class HelpCommand(Command):
if command.hidden:
continue
print(' %s: %s' % (command.name, command.summary))
-
+ return SUCCESS
HelpCommand()
diff --git a/pip/commands/search.py b/pip/commands/search.py
index f1e6aafe1..0e72ab364 100644
--- a/pip/commands/search.py
+++ b/pip/commands/search.py
@@ -2,18 +2,15 @@ import sys
import textwrap
import pkg_resources
import pip.download
-from pip.basecommand import Command
+from pip.basecommand import Command, SUCCESS
from pip.util import get_terminal_size
from pip.log import logger
from pip.backwardcompat import xmlrpclib, reduce, cmp
from pip.exceptions import CommandError
+from pip.status_codes import NO_MATCHES_FOUND
from distutils.version import StrictVersion, LooseVersion
-class SearchCommandError(CommandError):
- pass
-
-
class SearchCommand(Command):
name = 'search'
usage = '%prog QUERY'
@@ -42,6 +39,9 @@ class SearchCommand(Command):
terminal_width = get_terminal_size()[0]
print_results(hits, terminal_width=terminal_width)
+ if pypi_hits:
+ return SUCCESS
+ return NO_MATCHES_FOUND
def search(self, query, index_url):
pypi = xmlrpclib.ServerProxy(index_url, pip.download.xmlrpclib_transport)
diff --git a/pip/download.py b/pip/download.py
index d3ccdc27e..1a57ddb96 100644
--- a/pip/download.py
+++ b/pip/download.py
@@ -184,8 +184,8 @@ class URLOpener(object):
self.prompting = prompting
proxy = self.get_proxy(proxystr)
if proxy:
- handler = urllib2.ProxyHandler({"http": proxy, "ftp": proxy})
- opener = urllib2.build_opener(handler, urllib2.CacheFTPHandler)
+ proxy_support = urllib2.ProxyHandler({"http": proxy, "ftp": proxy, "https": proxy})
+ opener = urllib2.build_opener(proxy_support, urllib2.CacheFTPHandler)
urllib2.install_opener(opener)
def parse_credentials(self, netloc):
diff --git a/pip/status_codes.py b/pip/status_codes.py
new file mode 100644
index 000000000..b6208e964
--- /dev/null
+++ b/pip/status_codes.py
@@ -0,0 +1,5 @@
+SUCCESS = 0
+ERROR = 1
+UNKNOWN_ERROR = 2
+VIRTUALENV_NOT_FOUND = 3
+NO_MATCHES_FOUND = 23
diff --git a/tests/test_help.py b/tests/test_help.py
new file mode 100644
index 000000000..1fbe196c8
--- /dev/null
+++ b/tests/test_help.py
@@ -0,0 +1,62 @@
+from pip.exceptions import CommandError
+from pip.commands.help import (HelpCommand,
+ SUCCESS,
+ ERROR,)
+from mock import Mock
+from nose.tools import assert_raises
+from tests.test_pip import run_pip, reset_env
+
+
+def test_run_method_should_return_sucess_when_finds_command_name():
+ """
+ Test HelpCommand.run for existing command
+ """
+ options_mock = Mock()
+ args = ('freeze',)
+ help_cmd = HelpCommand()
+ status = help_cmd.run(options_mock, args)
+ assert status == SUCCESS
+
+def test_run_method_should_return_sucess_when_command_name_not_specified():
+ """
+ Test HelpCommand.run when there are no args
+ """
+ options_mock = Mock()
+ args = ()
+ help_cmd = HelpCommand()
+ status = help_cmd.run(options_mock, args)
+ assert status == SUCCESS
+
+def test_run_method_should_raise_command_error_when_command_does_not_exist():
+ """
+ Test HelpCommand.run for non-existing command
+ """
+ options_mock = Mock()
+ args = ('mycommand',)
+ help_cmd = HelpCommand()
+ assert_raises(CommandError, help_cmd.run, options_mock, args)
+
+def test_help_command_should_exit_status_ok_when_command_exists():
+ """
+ Test `help` command for existing command
+ """
+ reset_env()
+ result = run_pip('help', 'freeze')
+ assert result.returncode == SUCCESS
+
+def test_help_command_should_exit_status_ok_when_no_command_is_specified():
+ """
+ Test `help` command for no command
+ """
+ reset_env()
+ result = run_pip('help')
+ assert result.returncode == SUCCESS
+
+def test_help_command_should_exit_status_error_when_command_does_not_exist():
+ """
+ Test `help` command for non-existing command
+ """
+ reset_env()
+ result = run_pip('help', 'mycommand', expect_error=True)
+ assert result.returncode == ERROR
+
diff --git a/tests/test_search.py b/tests/test_search.py
index 7dc884872..44c9fe970 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -2,7 +2,8 @@ import pip.download
from pip.commands.search import (compare_versions,
highest_version,
transform_hits,
- SearchCommand,)
+ SearchCommand)
+from pip.status_codes import NO_MATCHES_FOUND, SUCCESS
from pip.backwardcompat import xmlrpclib, b
from mock import Mock
from tests.test_pip import run_pip, reset_env, pyversion
@@ -66,6 +67,7 @@ def test_searching_through_Search_class():
"""
Verify if ``pip.vcs.Search`` uses tests xmlrpclib.Transport class
"""
+ original_xmlrpclib_transport = pip.download.xmlrpclib_transport
pip.download.xmlrpclib_transport = fake_transport = Mock()
query = 'mylittlequerythatdoesnotexists'
dumped_xmlrpc_request = b(xmlrpclib.dumps(({'name': query, 'summary': query}, 'or'), 'search'))
@@ -73,8 +75,11 @@ def test_searching_through_Search_class():
fake_transport.request.return_value = (expected,)
pypi_searcher = SearchCommand()
result = pypi_searcher.search(query, 'http://pypi.python.org/pypi')
- assert expected == result, result
- fake_transport.request.assert_called_with('pypi.python.org', '/pypi', dumped_xmlrpc_request, verbose=VERBOSE_FALSE)
+ try:
+ assert expected == result, result
+ fake_transport.request.assert_called_with('pypi.python.org', '/pypi', dumped_xmlrpc_request, verbose=VERBOSE_FALSE)
+ finally:
+ pip.download.xmlrpclib_transport = original_xmlrpclib_transport
def test_search_missing_argument():
@@ -84,3 +89,39 @@ def test_search_missing_argument():
env = reset_env(use_distribute=True)
result = run_pip('search', expect_error=True)
assert 'ERROR: Missing required argument (search query).' in result.stdout
+
+def test_run_method_should_return_sucess_when_find_packages():
+ """
+ Test SearchCommand.run for found package
+ """
+ options_mock = Mock()
+ options_mock.index = 'http://pypi.python.org/pypi'
+ search_cmd = SearchCommand()
+ status = search_cmd.run(options_mock, ('pip',))
+ assert status == SUCCESS
+
+def test_run_method_should_return_no_matches_found_when_does_not_find_packages():
+ """
+ Test SearchCommand.run for no matches
+ """
+ options_mock = Mock()
+ options_mock.index = 'http://pypi.python.org/pypi'
+ search_cmd = SearchCommand()
+ status = search_cmd.run(options_mock, ('non-existant-package',))
+ assert status == NO_MATCHES_FOUND, status
+
+def test_search_should_exit_status_code_zero_when_find_packages():
+ """
+ Test search exit status code for package found
+ """
+ env = reset_env(use_distribute=True)
+ result = run_pip('search', 'pip')
+ assert result.returncode == SUCCESS
+
+def test_search_exit_status_code_when_finds_no_package():
+ """
+ Test search exit status code for no matches
+ """
+ env = reset_env(use_distribute=True)
+ result = run_pip('search', 'non-existant-package', expect_error=True)
+ assert result.returncode == NO_MATCHES_FOUND