diff options
author | Jannis Leidel <jannis@leidel.info> | 2012-01-07 17:34:15 +0100 |
---|---|---|
committer | Jannis Leidel <jannis@leidel.info> | 2012-01-07 17:34:15 +0100 |
commit | efa479c50249b00493807a325f2713c592306fcb (patch) | |
tree | 5a8d417baf48943b9bff309ea030b62d3a4c27b4 | |
parent | 550f6705d5b53d5b526f97b163a2e8a28966d7fe (diff) | |
parent | 6d6ead7923b80271243dc59225b5a29b87f28306 (diff) | |
download | pip-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.txt | 1 | ||||
-rw-r--r-- | docs/news.txt | 2 | ||||
-rw-r--r-- | docs/requirements.txt | 19 | ||||
-rw-r--r-- | docs/usage.txt | 17 | ||||
-rw-r--r-- | pip/basecommand.py | 25 | ||||
-rw-r--r-- | pip/commands/help.py | 12 | ||||
-rw-r--r-- | pip/commands/search.py | 10 | ||||
-rw-r--r-- | pip/download.py | 4 | ||||
-rw-r--r-- | pip/status_codes.py | 5 | ||||
-rw-r--r-- | tests/test_help.py | 62 | ||||
-rw-r--r-- | tests/test_search.py | 47 |
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 |