diff --git a/binstar_client/scripts/cli.py b/binstar_client/scripts/cli.py index 6b92f636..f4b2fc9b 100644 --- a/binstar_client/scripts/cli.py +++ b/binstar_client/scripts/cli.py @@ -1,40 +1,47 @@ -# pylint: disable=redefined-outer-name,unspecified-encoding,missing-class-docstring,missing-function-docstring +# -*- coding: utf8 -*- -""" -Anaconda repository command line manager -""" +"""Anaconda repository command line manager.""" -from __future__ import print_function, unicode_literals +from __future__ import annotations +__all__ = ('main',) + +import argparse +from importlib import metadata import logging +import os import sys -from argparse import ArgumentParser, RawDescriptionHelpFormatter -from importlib.metadata import entry_points -from logging.handlers import RotatingFileHandler -from os import makedirs -from os.path import join, exists, isfile +import types +import typing -import urllib3 from clyent import add_subparser_modules -from binstar_client import __version__ as version -from binstar_client import commands as command_module +from binstar_client import __version__ +from binstar_client import commands from binstar_client import errors from binstar_client.commands.login import interactive_login -from binstar_client.utils import USER_LOGDIR +from binstar_client.utils import logging_utils + logger = logging.getLogger('binstar') -def file_or_token(value): +def file_or_token(value: str) -> str: """ - If value is a file path and the file exists its contents are stripped and returned, otherwise value is returned. - """ - if isfile(value): - with open(value) as file: - return file.read().strip() + Retrieve a token from input. - if any(char in value for char in '/\\.'): + If :code:`value` is a path to a valid file - content of this file will be returned. Otherwise - value itself is + returned. + """ + if os.path.isfile(value): + stream: typing.TextIO + with open(value, 'rt', encoding='utf8') as stream: + result: str = stream.read(8193) + if len(result) > 8192: + raise ValueError('file is too large for a token') + return result.strip() + + if not set('/\\.').isdisjoint(value): # This chars will never be in a token value, but may be in a path # The error message will be handled by the parser raise ValueError() @@ -42,125 +49,86 @@ def file_or_token(value): return value -def _custom_excepthook(logger, show_traceback=False): - def excepthook(exc_type, exc_value, exc_traceback): - if issubclass(exc_type, KeyboardInterrupt): - return - - if show_traceback: - logger.error('', exc_info=(exc_type, exc_value, exc_traceback)) - else: - logger.error('%s', exc_value) - - return excepthook - - -class ConsoleFormatter(logging.Formatter): - def format(self, record): - fmt = '%(message)s' if record.levelno == logging.INFO \ - else '[%(levelname)s] %(message)s' - self._style._fmt = fmt # pylint: disable=protected-access - return super().format(record) - - -def _setup_logging(logger, log_level=logging.INFO, show_traceback=False, disable_ssl_warnings=False): - logger.setLevel(logging.DEBUG) - - if not exists(USER_LOGDIR): - makedirs(USER_LOGDIR) - - log_file = join(USER_LOGDIR, 'cli.log') - - file_handler = RotatingFileHandler(log_file, maxBytes=10 * (1024 ** 2), backupCount=5) - file_handler.setLevel(logging.DEBUG) - - console_handler = logging.StreamHandler() - console_handler.setLevel(log_level) - - console_handler.setFormatter(ConsoleFormatter()) - file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(name)-15s %(message)s')) - - logger.addHandler(console_handler) - logger.addHandler(file_handler) - - sys.excepthook = _custom_excepthook(logger, show_traceback=show_traceback) - - if disable_ssl_warnings: - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - -def add_default_arguments(parser, version=None): - output_group = parser.add_argument_group('output') - output_group.add_argument('--disable-ssl-warnings', action='store_true', default=False, - help='Disable SSL warnings (default: %(default)s)') - output_group.add_argument('--show-traceback', action='store_true', - help='Show the full traceback for chalmers user errors (default: %(default)s)') - output_group.add_argument('-v', '--verbose', - action='store_const', help='print debug information on the console', - dest='log_level', - default=logging.INFO, const=logging.DEBUG) - output_group.add_argument('-q', '--quiet', - action='store_const', help='Only show warnings or errors on the console', - dest='log_level', const=logging.WARNING) - - if version: - parser.add_argument('-V', '--version', action='version', - version='%%(prog)s Command line client (version %s)' % (version,)) - - -def binstar_main(sub_command_module, args=None, exit=True, # pylint: disable=redefined-builtin,too-many-arguments - description=None, version=None, epilog=None): - parser = ArgumentParser(description=description, epilog=epilog, - formatter_class=RawDescriptionHelpFormatter) - - add_default_arguments(parser, version) - bgroup = parser.add_argument_group('anaconda-client options') - bgroup.add_argument('-t', '--token', type=file_or_token, - help='Authentication token to use. ' - 'May be a token or a path to a file containing a token') - bgroup.add_argument('-s', '--site', - help='select the anaconda-client site to use', default=None) +def binstar_main( + sub_command_module: types.ModuleType, + args: typing.Optional[typing.Sequence[str]] = None, + exit_: bool = True, +) -> int: + """Run `anaconda-client` cli utility.""" + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + group = parser.add_argument_group('output') + group.add_argument( + '--disable-ssl-warnings', action='store_true', default=False, + help='Disable SSL warnings (default: %(default)s)', + ) + group.add_argument( + '--show-traceback', action='store_true', + help='Show the full traceback for chalmers user errors (default: %(default)s)', + ) + group.add_argument( + '-v', '--verbose', action='store_const', dest='log_level', default=logging.INFO, const=logging.DEBUG, + help='print debug information on the console', + ) + group.add_argument( + '-q', '--quiet', action='store_const', dest='log_level', const=logging.WARNING, + help='Only show warnings or errors on the console', + ) + + group = parser.add_argument_group('anaconda-client options') + group.add_argument( + '-t', '--token', type=file_or_token, + help='Authentication token to use. May be a token or a path to a file containing a token', + ) + group.add_argument('-s', '--site', default=None, help='select the anaconda-client site to use') + + if __version__: + parser.add_argument( + '-V', '--version', action='version', version=f'%(prog)s Command line client (version {__version__})', + ) add_subparser_modules(parser, sub_command_module, 'conda_server.subcommand') - args = parser.parse_args(args) + arguments: argparse.Namespace = parser.parse_args(args) - _setup_logging(logger, log_level=args.log_level, show_traceback=args.show_traceback, - disable_ssl_warnings=args.disable_ssl_warnings) + logging_utils.setup_logging( + logger, + log_level=arguments.log_level, + show_traceback=arguments.show_traceback, + disable_ssl_warnings=arguments.disable_ssl_warnings, + ) try: try: - if not hasattr(args, 'main'): - parser.error( - 'A sub command must be given. To show all available sub commands, run:\n\n\t anaconda -h\n', - ) - return args.main(args) + if hasattr(arguments, 'main'): + return arguments.main(arguments) + parser.error('A sub command must be given. To show all available sub commands, run:\n\n\t anaconda -h\n') except errors.Unauthorized: - if not sys.stdin.isatty() or args.token: - # Don't try the interactive login - # Just exit - raise - + if arguments.token or (not sys.stdin.isatty()): + raise # Don't try the interactive login, just exit logger.info('The action you are performing requires authentication, please sign in:') - interactive_login(args) - return args.main(args) + interactive_login(arguments) + return arguments.main(arguments) except errors.ShowHelp as error: - args.sub_parser.print_help() - if exit: + arguments.sub_parser.print_help() + if exit_: raise SystemExit(1) from error return 1 + return 0 # type: ignore -def _load_main_plugin(): +def _load_main_plugin() -> typing.Optional[typing.Callable[[], typing.Any]]: """Allow loading a new CLI main entrypoint via plugin mechanisms. There can only be one.""" - - plugin_group_name = 'anaconda_cli.main' + plugin_group_name: typing.Final[str] = 'anaconda_cli.main' # The API was changed in Python 3.10, see https://docs.python.org/3/library/importlib.metadata.html#entry-points - if sys.version_info.major == 3 and sys.version_info.minor <= 9: - plugin_mains = entry_points().get(plugin_group_name, []) + plugin_mains: typing.List[metadata.EntryPoint] + if sys.version_info.major == 3 and sys.version_info.minor < 10: + plugin_mains = metadata.entry_points().get(plugin_group_name, []) else: - plugin_mains = entry_points().select(group=plugin_group_name) + plugin_mains = metadata.entry_points().select(group=plugin_group_name) # type: ignore if len(plugin_mains) > 1: raise EnvironmentError( @@ -178,13 +146,20 @@ def _load_main_plugin(): return None -def main(args=None, _exit=True, allow_plugin_main=True): - plugged_in_main = _load_main_plugin() - if allow_plugin_main and plugged_in_main is not None: - plugged_in_main() - else: - binstar_main(command_module, args, _exit, - description=__doc__, version=version) +def main( + args: typing.Optional[typing.Sequence[str]] = None, + *, + exit_: bool = True, + allow_plugin_main: bool = True, +) -> None: + """Entrypoint for CLI interface of `anaconda`.""" + if allow_plugin_main: + plugged_in_main: typing.Optional[typing.Callable[[], typing.Any]] = _load_main_plugin() + if plugged_in_main is not None: + plugged_in_main() + return + + binstar_main(commands, args, exit_) if __name__ == '__main__': diff --git a/binstar_client/utils/logging_utils.py b/binstar_client/utils/logging_utils.py new file mode 100644 index 00000000..26af2de0 --- /dev/null +++ b/binstar_client/utils/logging_utils.py @@ -0,0 +1,82 @@ +# -*- coding: utf8 -*- + +"""Utilities to configure logging for the application.""" + +from __future__ import annotations + +__all__ = ['setup_logging'] + +import logging.handlers +import os +import sys +import types +import typing + +import urllib3.exceptions + +from . import config + + +def _custom_excepthook( + logger: logging.Logger, + show_traceback: bool = False, +) -> typing.Callable[[typing.Type[BaseException], BaseException, typing.Optional[types.TracebackType]], None]: + """Generate custom exception hook to log captured exceptions.""" + def excepthook( + exc_type: typing.Type[BaseException], + exc_value: BaseException, + exc_traceback: typing.Optional[types.TracebackType], + ) -> None: + if issubclass(exc_type, KeyboardInterrupt): + return + if show_traceback: + logger.error('', exc_info=(exc_type, exc_value, exc_traceback)) + else: + logger.error('%s', exc_value) + + return excepthook + + +class ConsoleFormatter(logging.Formatter): + """Custom logging formatter.""" + + FORMAT_DEFAULT: typing.Final[str] = '[%(levelname)s] %(message)s' + FORMAT_CUSTOM: typing.Final[typing.Mapping[int, str]] = {logging.INFO: '%(message)s'} + + def format(self, record: logging.LogRecord) -> str: + """Format log record before printing it.""" + # pylint: disable=protected-access + self._style._fmt = self.FORMAT_CUSTOM.get(record.levelno, self.FORMAT_DEFAULT) + return super().format(record) + + +def setup_logging( + logger: logging.Logger, + log_level: int = logging.INFO, + show_traceback: bool = False, + disable_ssl_warnings: bool = False +) -> None: + """Configure logging for the application.""" + logger.setLevel(logging.DEBUG) + + os.makedirs(config.USER_LOGDIR, exist_ok=True) + log_file: str = os.path.join(config.USER_LOGDIR, 'cli.log') + + file_handler: logging.Handler = logging.handlers.RotatingFileHandler( + log_file, + maxBytes=10 * (1024 ** 2), + backupCount=5, + ) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(name)-15s %(message)s')) + logger.addHandler(file_handler) + + console_handler: logging.Handler = logging.StreamHandler() + console_handler.setLevel(log_level) + console_handler.setFormatter(ConsoleFormatter()) + logger.addHandler(console_handler) + + sys.excepthook = _custom_excepthook(logger, show_traceback=show_traceback) + + if disable_ssl_warnings: + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) diff --git a/setup.cfg b/setup.cfg index 9dc76dfa..f843e297 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ ignore_errors = False ignore_missing_imports = True namespace_packages = True no_implicit_optional = True -python_version = 3.7 +python_version = 3.8 strict_optional = True warn_no_return = True warn_redundant_casts = True diff --git a/tests/fixture.py b/tests/fixture.py index 641746f4..34fb0b71 100644 --- a/tests/fixture.py +++ b/tests/fixture.py @@ -30,7 +30,7 @@ def setUp(self): self.store_token_patch = mock.patch('binstar_client.utils.config.store_token') self.store_token = self.store_token_patch.start() - self.setup_logging_patch = mock.patch('binstar_client.scripts.cli._setup_logging') + self.setup_logging_patch = mock.patch('binstar_client.utils.logging_utils.setup_logging') self.setup_logging_patch.start() self.logger = logger = logging.getLogger('binstar') diff --git a/tests/test_authorizations.py b/tests/test_authorizations.py index 857d0a39..054cb586 100644 --- a/tests/test_authorizations.py +++ b/tests/test_authorizations.py @@ -19,7 +19,7 @@ def test_remove_token_from_org(self, urls): content='{"token": "a-token"}', status=201 ) - main(['--show-traceback', 'auth', '--remove', 'tokenname', '-o', 'orgname'], False) + main(['--show-traceback', 'auth', '--remove', 'tokenname', '-o', 'orgname'], exit_=False) self.assertIn('Removed token tokenname', self.stream.getvalue()) remove_token.assertCalled() @@ -32,7 +32,7 @@ def test_remove_token(self, urls): content='{"token": "a-token"}', status=201 ) - main(['--show-traceback', 'auth', '--remove', 'tokenname'], False) + main(['--show-traceback', 'auth', '--remove', 'tokenname'], exit_=False) self.assertIn('Removed token tokenname', self.stream.getvalue()) remove_token.assertCalled() @@ -46,7 +46,7 @@ def test_remove_token_forbidden(self, urls): status=403 ) with self.assertRaises(BinstarError): - main(['--show-traceback', 'auth', '--remove', 'tokenname', '-o', 'wrong_org'], False) + main(['--show-traceback', 'auth', '--remove', 'tokenname', '-o', 'wrong_org'], exit_=False) remove_token.assertCalled() diff --git a/tests/test_config.py b/tests/test_config.py index a70b4961..09bbe133 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -19,7 +19,7 @@ def test_write_env(self): with mock.patch('binstar_client.commands.config.USER_CONFIG', join(tmpdir, 'config.yaml')), \ mock.patch('binstar_client.commands.config.SEARCH_PATH', [tmpdir]): - main(['config', '--set', 'url', 'http://localhost:5000'], False) + main(['config', '--set', 'url', 'http://localhost:5000'], exit_=False) self.assertTrue(exists(join(tmpdir, 'config.yaml'))) @@ -28,7 +28,7 @@ def test_write_env(self): expected_config_output = 'url: http://localhost:5000\n' self.assertEqual(config_output, expected_config_output) - main(['config', '--show-sources'], False) + main(['config', '--show-sources'], exit_=False) expected_show_sources_output = '==> {config} <==\nurl: http://localhost:5000\n\n'.format( config=join(tmpdir, 'config.yaml')) diff --git a/tests/test_copy.py b/tests/test_copy.py index eae97910..6cc0660a 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -20,7 +20,7 @@ def test_copy_label(self, urls): copy = urls.register( method='POST', path='/copy/package/u1/p1/1.0/', content='[{"basename": "copied-file_1.0.tgz"}]') - main(['--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0'], False) + main(['--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0'], exit_=False) urls.assertAllCalled() req = json.loads(copy.req.body) @@ -33,9 +33,9 @@ def test_copy_replace(self, urls): copy = urls.register( method='PUT', path='/copy/package/u1/p1/1.0/', content='[{"basename": "copied-file_1.0.tgz"}]') - main( - ['--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0', '--replace'], - False) + main([ + '--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0', '--replace', + ], exit_=False) urls.assertAllCalled() req = json.loads(copy.req.body) @@ -48,8 +48,9 @@ def test_copy_update(self, urls): copy = urls.register( method='PATCH', path='/copy/package/u1/p1/1.0/', content='[{"basename": "copied-file_1.0.tgz"}]') - main(['--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0', '--update'], - False) + main([ + '--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0', '--update', + ], exit_=False) urls.assertAllCalled() req = json.loads(copy.req.body) @@ -63,7 +64,9 @@ def test_copy_file_conflict(self, urls): method='POST', path='/copy/package/u1/p1/1.0/', status=409 ) with self.assertRaises(Conflict): - main(['--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0'], False) + main([ + '--show-traceback', 'copy', '--from-label', 'dev', '--to-label', 'release/xyz', 'u1/p1/1.0', + ], exit_=False) urls.assertAllCalled() req = json.loads(copy.req.body) @@ -76,7 +79,8 @@ def test_copy_argument_error(self, urls): with self.assertRaises(SystemExit): main([ '--show-traceback', 'copy', '--from-label', 'dev', - '--to-label', 'release/xyz', 'u1/p1/1.0', '--update', '--replace'], False) + '--to-label', 'release/xyz', 'u1/p1/1.0', '--update', '--replace', + ], exit_=False) if __name__ == '__main__': diff --git a/tests/test_groups.py b/tests/test_groups.py index 5c355e3d..884d4263 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -16,7 +16,7 @@ def test_show(self, urls): content='{"groups": [{"name":"grp", "permission": "read"}]}', ) - main(['--show-traceback', 'groups', 'show', 'org'], False) + main(['--show-traceback', 'groups', 'show', 'org'], exit_=False) urls.assertAllCalled() @@ -28,7 +28,7 @@ def test_show_group(self, urls): content='{"name": "owners", "permission": "read", "members_count": 1, "repos_count": 1}', ) - main(['--show-traceback', 'groups', 'show', 'org/owners'], False) + main(['--show-traceback', 'groups', 'show', 'org/owners'], exit_=False) urls.assertAllCalled() @@ -40,14 +40,14 @@ def test_create(self, urls): status=204, ) - main(['--show-traceback', 'groups', 'add', 'org/new_grp'], False) + main(['--show-traceback', 'groups', 'add', 'org/new_grp'], exit_=False) urls.assertAllCalled() @urlpatch def test_create_missing_group(self, urls): with self.assertRaisesRegex(errors.UserError, 'Group name not given'): - main(['--show-traceback', 'groups', 'add', 'org'], False) + main(['--show-traceback', 'groups', 'add', 'org'], exit_=False) @urlpatch def test_add_member(self, urls): @@ -57,14 +57,14 @@ def test_add_member(self, urls): status=204, ) - main(['--show-traceback', 'groups', 'add_member', 'org/grp/new_member'], False) + main(['--show-traceback', 'groups', 'add_member', 'org/grp/new_member'], exit_=False) urls.assertAllCalled() @urlpatch def test_add_member_missing_member(self, urls): with self.assertRaisesRegex(errors.UserError, 'Member name not given'): - main(['--show-traceback', 'groups', 'add_member', 'org/grp'], False) + main(['--show-traceback', 'groups', 'add_member', 'org/grp'], exit_=False) @urlpatch def test_remove_member(self, urls): @@ -74,7 +74,7 @@ def test_remove_member(self, urls): status=204, ) - main(['--show-traceback', 'groups', 'remove_member', 'org/grp/new_member'], False) + main(['--show-traceback', 'groups', 'remove_member', 'org/grp/new_member'], exit_=False) urls.assertAllCalled() @@ -86,7 +86,7 @@ def test_packages(self, urls): content='[{"name": "pkg", "full_name": "org/pkg", "summary": "An org pkg"}]' ) - main(['--show-traceback', 'groups', 'packages', 'org/grp'], False) + main(['--show-traceback', 'groups', 'packages', 'org/grp'], exit_=False) urls.assertAllCalled() @@ -98,7 +98,7 @@ def test_add_package(self, urls): status=204, ) - main(['--show-traceback', 'groups', 'add_package', 'org/grp/pkg'], False) + main(['--show-traceback', 'groups', 'add_package', 'org/grp/pkg'], exit_=False) urls.assertAllCalled() @@ -110,7 +110,7 @@ def test_remove_package(self, urls): status=204, ) - main(['--show-traceback', 'groups', 'remove_package', 'org/grp/pkg'], False) + main(['--show-traceback', 'groups', 'remove_package', 'org/grp/pkg'], exit_=False) urls.assertAllCalled() diff --git a/tests/test_login.py b/tests/test_login.py index 6430f8c5..df637c0a 100644 --- a/tests/test_login.py +++ b/tests/test_login.py @@ -27,7 +27,7 @@ def test_login(self, urls, data, getpass, store_token): urls.register(path='/authentication-type', content='{"authentication_type": "password"}') auth = urls.register(method='POST', path='/authentications', content='{"token": "a-token"}') - main(['--show-traceback', 'login'], False) + main(['--show-traceback', 'login'], exit_=False) self.assertIn('login successful', self.stream.getvalue()) auth.assertCalled() @@ -50,7 +50,7 @@ def test_login_compatible(self, urls, data, getpass, store_token): urls.register(path='/authentication-type', status=404) auth = urls.register(method='POST', path='/authentications', content='{"token": "a-token"}') - main(['--show-traceback', 'login'], False) + main(['--show-traceback', 'login'], exit_=False) self.assertIn('login successful', self.stream.getvalue()) auth.assertCalled() diff --git a/tests/test_register.py b/tests/test_register.py index 1546d88f..2d4b27b7 100644 --- a/tests/test_register.py +++ b/tests/test_register.py @@ -25,7 +25,7 @@ def test_register_public(self, registry): # pylint: disable=missing-function-do r2 = registry.register(method='GET', path='/package/eggs/foo', status=404) r3 = registry.register(method='POST', path='/package/eggs/foo', status=200, content='{"login": "eggs"}') - main(['--show-traceback', 'register', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'register', data_dir('foo-0.1-0.tar.bz2')], exit_=False) r1.assertCalled() r2.assertCalled() @@ -40,7 +40,7 @@ def test_register_private(self, registry): # pylint: disable=missing-function-d r2 = registry.register(method='GET', path='/package/eggs/foo', status=404) r3 = registry.register(method='POST', path='/package/eggs/foo', status=200, content='{"login": "eggs"}') - main(['--show-traceback', 'register', '--private', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'register', '--private', data_dir('foo-0.1-0.tar.bz2')], exit_=False) r1.assertCalled() r2.assertCalled() diff --git a/tests/test_update.py b/tests/test_update.py index 3e7a4df2..36f9056f 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -35,7 +35,7 @@ class TestUpdate(CLITestCase): def test_update_package_from_json(self, urls): urls.register(method='HEAD', path='/', status=200) update = urls.register(method='PATCH', path='/package/owner/package_name', content=json_test_data, status=200) - main(['update', 'owner/package_name', data_dir('metadata.json')], False) + main(['update', 'owner/package_name', data_dir('metadata.json')], exit_=False) urls.assertAllCalled() req = json.loads(update.req.body) @@ -46,7 +46,7 @@ def test_update_package_from_file(self, urls): urls.register(method='HEAD', path='/', status=200) update = urls.register(method='PATCH', path='/package/owner/package_name', content=package_test_data, status=200) - main(['update', 'owner/package_name', data_dir('test_package34-0.3.1.tar.gz')], False) + main(['update', 'owner/package_name', data_dir('test_package34-0.3.1.tar.gz')], exit_=False) urls.assertAllCalled() req = json.loads(update.req.body) @@ -57,7 +57,7 @@ def test_update_release_from_json(self, urls): urls.register(method='HEAD', path='/', status=200) update = urls.register(method='PATCH', path='/release/owner/package_name/1.0.0', content=json_test_data, status=200) - main(['update', 'owner/package_name/1.0.0', data_dir('metadata.json'), '--release'], False) + main(['update', 'owner/package_name/1.0.0', data_dir('metadata.json'), '--release'], exit_=False) urls.assertAllCalled() req = json.loads(update.req.body) @@ -68,7 +68,7 @@ def test_update_release_from_file(self, urls): urls.register(method='HEAD', path='/', status=200) update = urls.register(method='PATCH', path='/release/owner/package_name/1.0.0', content=release_test_data, status=200) - main(['update', 'owner/package_name/1.0.0', data_dir('test_package34-0.3.1.tar.gz'), '--release'], False) + main(['update', 'owner/package_name/1.0.0', data_dir('test_package34-0.3.1.tar.gz'), '--release'], exit_=False) urls.assertAllCalled() req = json.loads(update.req.body) @@ -80,7 +80,7 @@ def test_update_local_file_not_found(self, urls): urls.register(method='PATCH', path='/package/owner/package_name', content=package_test_data, status=404) with self.assertRaises(SystemExit): - main(['update', 'owner/package_name', data_dir('not_existing.tar.gz')], False) + main(['update', 'owner/package_name', data_dir('not_existing.tar.gz')], exit_=False) @urlpatch def test_update_package_not_found(self, urls): @@ -88,14 +88,14 @@ def test_update_package_not_found(self, urls): urls.register(method='PATCH', path='/package/owner/package_name', content=package_test_data, status=404) with self.assertRaises(errors.NotFound): - main(['update', 'owner/package_name', data_dir('test_package34-0.3.1.tar.gz')], False) + main(['update', 'owner/package_name', data_dir('test_package34-0.3.1.tar.gz')], exit_=False) @urlpatch def test_update_release_missing_version(self, urls): urls.register(method='HEAD', path='/', status=200) urls.register(method='PATCH', path='/release/owner/package_name/1.0.0', content=package_test_data, status=200) with self.assertRaises(errors.UserError): - main(['update', 'owner/package_name', data_dir('test_package34-0.3.1.tar.gz'), '--release'], False) + main(['update', 'owner/package_name', data_dir('test_package34-0.3.1.tar.gz'), '--release'], exit_=False) @urlpatch def test_update_release_not_found(self, urls): @@ -103,4 +103,6 @@ def test_update_release_not_found(self, urls): urls.register(method='PATCH', path='/release/owner/package_name/1.0.0', content=package_test_data, status=404) with self.assertRaises(errors.NotFound): - main(['update', 'owner/package_name/1.0.0', data_dir('test_package34-0.3.1.tar.gz'), '--release'], False) + main([ + 'update', 'owner/package_name/1.0.0', data_dir('test_package34-0.3.1.tar.gz'), '--release', + ], exit_=False) diff --git a/tests/test_upload.py b/tests/test_upload.py index 65c5d0d6..ee700577 100644 --- a/tests/test_upload.py +++ b/tests/test_upload.py @@ -31,7 +31,7 @@ def test_upload_bad_package(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/commit/eggs/foo/0.1/osx-64/foo-0.1-0.tar.bz2', status=200, content={}) - main(['--show-traceback', 'upload', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'upload', data_dir('foo-0.1-0.tar.bz2')], exit_=False) self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -42,7 +42,7 @@ def test_upload_bad_package_no_register(self, registry): registry.register(method='GET', path='/package/eggs/foo', status=404) with self.assertRaises(errors.UserError): - main(['--show-traceback', 'upload', '--no-register', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'upload', '--no-register', data_dir('foo-0.1-0.tar.bz2')], exit_=False) registry.assertAllCalled() @@ -62,7 +62,7 @@ def test_upload_conda(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/commit/eggs/foo/0.1/osx-64/foo-0.1-0.tar.bz2', status=200, content={}) - main(['--show-traceback', 'upload', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'upload', data_dir('foo-0.1-0.tar.bz2')], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -85,7 +85,7 @@ def test_upload_conda_v2(self, registry): registry.register( method='POST', path='/commit/eggs/mock/2.0.0/osx-64/mock-2.0.0-py37_1000.conda', status=200, content={}) - main(['--show-traceback', 'upload', data_dir('mock-2.0.0-py37_1000.conda')], False) + main(['--show-traceback', 'upload', data_dir('mock-2.0.0-py37_1000.conda')], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -109,7 +109,9 @@ def test_upload_use_pkg_metadata(self, registry): registry.register( method='POST', path='/commit/eggs/mock/2.0.0/osx-64/mock-2.0.0-py37_1000.conda', status=200, content={}) - main(['--show-traceback', 'upload', '--force-metadata-update', data_dir('mock-2.0.0-py37_1000.conda')], False) + main([ + '--show-traceback', 'upload', '--force-metadata-update', data_dir('mock-2.0.0-py37_1000.conda'), + ], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -133,7 +135,7 @@ def test_upload_pypi(self, registry): registry.register(method='POST', path='/commit/eggs/test-package34/0.3.1/test_package34-0.3.1.tar.gz', status=200, content={}) - main(['--show-traceback', 'upload', data_dir('test_package34-0.3.1.tar.gz')], False) + main(['--show-traceback', 'upload', data_dir('test_package34-0.3.1.tar.gz')], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -158,10 +160,10 @@ def test_upload_pypi_with_conda_package_name_allowed(self, registry): status=200, content={}) # Pass -o to override the channel/package pypi package should go to - main(['--show-traceback', 'upload', - '--package', 'test_package34', - '--package-type', 'pypi', data_dir('test_package34-0.3.1.tar.gz')], False) - + main([ + '--show-traceback', 'upload', '--package', 'test_package34', '--package-type', 'pypi', + data_dir('test_package34-0.3.1.tar.gz'), + ], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -172,10 +174,10 @@ def test_upload_conda_package_with_name_override_fails(self, registry): # Passing -o for `file` package_type doesn't override channel with self.assertRaises(errors.BinstarError): - main(['--show-traceback', 'upload', - '--package', 'test_package', - '--package-type', 'file', - data_dir('test_package34-0.3.1.tar.gz')], False) + main([ + '--show-traceback', 'upload', '--package', 'test_package', '--package-type', 'file', + data_dir('test_package34-0.3.1.tar.gz'), + ], exit_=False) registry.assertAllCalled() @@ -185,9 +187,9 @@ def test_upload_pypi_with_random_name(self, registry): registry.register(method='GET', path='/user', content='{"login": "eggs"}') with self.assertRaises(errors.BinstarError): - main(['--show-traceback', 'upload', - '--package', 'alpha_omega', - data_dir('test_package34-0.3.1.tar.gz')], False) + main([ + '--show-traceback', 'upload', '--package', 'alpha_omega', data_dir('test_package34-0.3.1.tar.gz'), + ], exit_=False) registry.assertAllCalled() @@ -209,11 +211,10 @@ def test_upload_file(self, registry): registry.register(method='POST', path='/commit/eggs/test-package34/0.3.1/test_package34-0.3.1.tar.gz', status=200, content={}) - main(['--show-traceback', 'upload', - '--package-type', 'file', - '--package', 'test-package34', - '--version', '0.3.1', - data_dir('test_package34-0.3.1.tar.gz')], False) + main([ + '--show-traceback', 'upload', '--package-type', 'file', '--package', 'test-package34', '--version', '0.3.1', + data_dir('test_package34-0.3.1.tar.gz'), + ], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -233,9 +234,7 @@ def test_upload_project(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/apps/eggs/projects/dog/commit/dist42', content='{}') - main(['--show-traceback', 'upload', - '--package-type', 'project', - data_dir('bar')], False) + main(['--show-traceback', 'upload', '--package-type', 'project', data_dir('bar')], exit_=False) registry.assertAllCalled() @@ -251,9 +250,7 @@ def test_upload_notebook_as_project(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/apps/eggs/projects/foo/commit/dist42', content='{}') - main(['--show-traceback', 'upload', - '--package-type', 'project', - data_dir('foo.ipynb')], False) + main(['--show-traceback', 'upload', '--package-type', 'project', data_dir('foo.ipynb')], exit_=False) registry.assertAllCalled() @@ -280,7 +277,7 @@ def test_upload_notebook_as_package(self, registry): with patch('binstar_client.inspect_package.ipynb.datetime') as mock_datetime: mock_datetime.now.return_value = test_datetime - main(['--show-traceback', 'upload', data_dir('foo.ipynb')], False) + main(['--show-traceback', 'upload', data_dir('foo.ipynb')], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -296,10 +293,9 @@ def test_upload_project_specifying_user(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/apps/alice/projects/dog/commit/dist42', content='{}') - main(['--show-traceback', 'upload', - '--package-type', 'project', - '--user', 'alice', - data_dir('bar')], False) + main([ + '--show-traceback', 'upload', '--package-type', 'project', '--user', 'alice', data_dir('bar'), + ], exit_=False) registry.assertAllCalled() @@ -316,10 +312,9 @@ def test_upload_project_specifying_token(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/apps/eggs/projects/dog/commit/dist42', content='{}') - main(['--show-traceback', '--token', 'abcdefg', - 'upload', - '--package-type', 'project', - data_dir('bar')], False) + main([ + '--show-traceback', '--token', 'abcdefg', 'upload', '--package-type', 'project', data_dir('bar'), + ], exit_=False) registry.assertAllCalled() @@ -337,7 +332,7 @@ def test_upload_interactive_no_overwrite(self, registry, bool_input): # don't overwrite bool_input.return_value = False - main(['--show-traceback', 'upload', '-i', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'upload', '-i', data_dir('foo-0.1-0.tar.bz2')], exit_=False) @urlpatch def test_upload_private_package(self, registry): @@ -356,7 +351,7 @@ def test_upload_private_package(self, registry): registry.register(method='POST', path='/s3_url', status=201) registry.register(method='POST', path='/commit/eggs/foo/0.1/osx-64/foo-0.1-0.tar.bz2', status=200, content={}) - main(['--show-traceback', 'upload', '--private', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'upload', '--private', data_dir('foo-0.1-0.tar.bz2')], exit_=False) registry.assertAllCalled() self.assertIsNotNone(json.loads(staging_response.req.body).get('sha256')) @@ -370,7 +365,7 @@ def test_upload_private_package_not_allowed(self, registry): content='{"error": "You can not create a private package."}', status=400) with self.assertRaises(errors.BinstarError): - main(['--show-traceback', 'upload', '--private', data_dir('foo-0.1-0.tar.bz2')], False) + main(['--show-traceback', 'upload', '--private', data_dir('foo-0.1-0.tar.bz2')], exit_=False) if __name__ == '__main__': diff --git a/tests/test_whoami.py b/tests/test_whoami.py index 2487388a..378dbd3a 100644 --- a/tests/test_whoami.py +++ b/tests/test_whoami.py @@ -25,7 +25,7 @@ class Test(CLITestCase): @urlpatch def test_whoami_anon(self, urls): user = urls.register(method='GET', path='/user', status=401) - main(['--show-traceback', 'whoami'], False) + main(['--show-traceback', 'whoami'], exit_=False) self.assertIn('Anonymous User', self.stream.getvalue()) user.assertCalled() @@ -35,7 +35,7 @@ def test_whoami(self, urls): content = json.dumps({'login': 'eggs', 'created_at': '1/2/2000'}) user = urls.register(method='GET', path='/user', content=content) - main(['--show-traceback', 'whoami'], False) + main(['--show-traceback', 'whoami'], exit_=False) self.assertIn('eggs', self.stream.getvalue()) user.assertCalled() @@ -55,7 +55,7 @@ def test_netrc_ignored(self, urls): user = urls.register(path='/user', status=401) - main(['--show-traceback', 'whoami'], False) + main(['--show-traceback', 'whoami'], exit_=False) self.assertNotIn('Authorization', user.req.headers)