"""Base Command class, and related routines""" import os import socket import sys import tempfile import traceback import time import optparse from pip.log import logger from pip.download import urlopen from pip.exceptions import (BadCommand, InstallationError, UninstallationError, CommandError, PreviousBuildDirError) from pip.backwardcompat import StringIO from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter from pip.status_codes import (SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND, PREVIOUS_BUILD_DIR_ERROR) from pip.util import get_prog __all__ = ['Command'] # for backwards compatibiliy get_proxy = urlopen.get_proxy class Command(object): name = None usage = None hidden = False def __init__(self, main_parser): parser_kw = { 'usage': self.usage, 'prog': '%s %s' % (get_prog(), self.name), 'formatter': UpdatingDefaultsHelpFormatter(), 'add_help_option': False, 'name': self.name, 'description': self.__doc__, } self.main_parser = main_parser self.parser = ConfigOptionParser(**parser_kw) # Commands should add options to this option group optgroup_name = '%s Options' % self.name.capitalize() self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name) # Re-add all options and option groups. for group in main_parser.option_groups: self._copy_option_group(self.parser, group) # Copies all general options from the main parser. self._copy_options(self.parser, main_parser.option_list) def _copy_options(self, parser, options): """Populate an option parser or group with options.""" for option in options: if not option.dest: continue parser.add_option(option) def _copy_option_group(self, parser, group): """Copy option group (including options) to another parser.""" new_group = optparse.OptionGroup(parser, group.title) self._copy_options(new_group, group.option_list) parser.add_option_group(new_group) def setup_logging(self): pass def main(self, args, initial_options): options, args = self.parser.parse_args(args) level = 1 # Notify level += options.verbose level -= options.quiet level = logger.level_for_integer(4 - level) complete_log = [] logger.add_consumers( (level, sys.stdout), (logger.DEBUG, complete_log.append), ) if options.log_explicit_levels: logger.explicit_levels = True self.setup_logging() #TODO: try to get these passing down from the command? # without resorting to os.environ to hold these. if options.no_input: os.environ['PIP_NO_INPUT'] = '1' if options.exists_action: os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) if options.cert: os.environ['PIP_CERT'] = options.cert if options.require_venv: # If a venv is required check if it can really be found if not os.environ.get('VIRTUAL_ENV'): logger.fatal('Could not find an activated virtualenv (required).') sys.exit(VIRTUALENV_NOT_FOUND) if options.log: log_fp = open_logfile(options.log, 'a') logger.add_consumers((logger.DEBUG, log_fp)) else: log_fp = None socket.setdefaulttimeout(options.timeout or None) urlopen.setup(proxystr=options.proxy, prompting=not options.no_input) exit = SUCCESS store_log = False try: 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 PreviousBuildDirError: e = sys.exc_info()[1] logger.fatal(str(e)) logger.info('Exception information:\n%s' % format_exc()) store_log = True exit = PREVIOUS_BUILD_DIR_ERROR except (InstallationError, UninstallationError): e = sys.exc_info()[1] logger.fatal(str(e)) logger.info('Exception information:\n%s' % format_exc()) store_log = True 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 = ERROR except CommandError: e = sys.exc_info()[1] logger.fatal('ERROR: %s' % e) logger.info('Exception information:\n%s' % format_exc()) exit = ERROR except KeyboardInterrupt: logger.fatal('Operation cancelled by user') logger.info('Exception information:\n%s' % format_exc()) store_log = True exit = ERROR except: logger.fatal('Exception:\n%s' % format_exc()) store_log = True exit = UNKNOWN_ERROR if log_fp is not None: log_fp.close() if store_log: log_fn = options.log_file text = '\n'.join(complete_log) try: log_fp = open_logfile(log_fn, 'w') except IOError: 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) log_fp.write(text) log_fp.close() return exit def format_exc(exc_info=None): if exc_info is None: exc_info = sys.exc_info() out = StringIO() traceback.print_exception(*exc_info, **dict(file=out)) return out.getvalue() def open_logfile(filename, mode='a'): """Open the named log file in append mode. If the file already exists, a separator will also be printed to the file to separate past activity from current activity. """ filename = os.path.expanduser(filename) filename = os.path.abspath(filename) dirname = os.path.dirname(filename) if not os.path.exists(dirname): os.makedirs(dirname) exists = os.path.exists(filename) log_fp = open(filename, mode) if exists: log_fp.write('%s\n' % ('-' * 60)) log_fp.write('%s run on %s\n' % (sys.argv[0], time.strftime('%c'))) return log_fp