From be4baba19ed5370935675e3455b5939b5bf4f598 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 10 Feb 2016 15:09:25 +0100 Subject: [PATCH] Merge CliError into UserError --- tests/cli/test_config.py | 14 +++++++------- vdirsyncer/cli/__init__.py | 15 --------------- vdirsyncer/cli/config.py | 33 +++++++++++++++++++-------------- vdirsyncer/cli/tasks.py | 11 +++++++---- vdirsyncer/cli/utils.py | 24 ++++++++++++++---------- vdirsyncer/exceptions.py | 9 +++++++++ 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/tests/cli/test_config.py b/tests/cli/test_config.py index 0a76d39..c07a5e0 100644 --- a/tests/cli/test_config.py +++ b/tests/cli/test_config.py @@ -4,7 +4,7 @@ from textwrap import dedent import pytest import vdirsyncer.cli.utils # noqa -from vdirsyncer import cli +from vdirsyncer import cli, exceptions from vdirsyncer.cli.config import parse_config_value, \ read_config as _read_config @@ -70,7 +70,7 @@ def test_storage_instance_from_config(monkeypatch): def test_missing_general_section(read_config): - with pytest.raises(cli.CliError) as excinfo: + with pytest.raises(exceptions.UserError) as excinfo: read_config(u''' [pair my_pair] a = my_a @@ -87,17 +87,17 @@ def test_missing_general_section(read_config): fileext = .txt ''') - assert 'Invalid general section.' in excinfo.value.msg + assert 'Invalid general section.' in str(excinfo.value) def test_wrong_general_section(read_config): - with pytest.raises(cli.CliError) as excinfo: + with pytest.raises(exceptions.UserError) as excinfo: read_config(u''' [general] wrong = true ''') - assert 'Invalid general section.' in excinfo.value.msg + assert 'Invalid general section.' in str(excinfo.value) assert excinfo.value.problems == [ 'general section doesn\'t take the parameters: wrong', 'general section is missing the parameters: status_path' @@ -112,7 +112,7 @@ def test_invalid_storage_name(): [storage foo.bar] ''')) - with pytest.raises(cli.CliError) as excinfo: + with pytest.raises(exceptions.UserError) as excinfo: _read_config(f) assert 'invalid characters' in str(excinfo.value).lower() @@ -169,7 +169,7 @@ def test_invalid_collections_arg(): fileext = .txt ''')) - with pytest.raises(cli.utils.CliError) as excinfo: + with pytest.raises(exceptions.UserError) as excinfo: _read_config(f) assert ( diff --git a/vdirsyncer/cli/__init__.py b/vdirsyncer/cli/__init__.py index 2983c1e..74d6e1f 100644 --- a/vdirsyncer/cli/__init__.py +++ b/vdirsyncer/cli/__init__.py @@ -24,21 +24,6 @@ class AppContext(object): pass_context = click.make_pass_decorator(AppContext, ensure=True) -class CliError(RuntimeError): - def __init__(self, msg, problems=None): - self.msg = msg - self.problems = problems - RuntimeError.__init__(self, msg) - - def __str__(self): - msg = self.msg - li = u'\n - ' - for problem in self.problems or (): - msg += u'{}{}'.format(li, problem) - - return msg - - def catch_errors(f): @functools.wraps(f) def inner(*a, **kw): diff --git a/vdirsyncer/cli/config.py b/vdirsyncer/cli/config.py index 63dd071..3e1333a 100644 --- a/vdirsyncer/cli/config.py +++ b/vdirsyncer/cli/config.py @@ -3,9 +3,9 @@ import os import string from itertools import chain -from . import CliError, cli_logger +from . import cli_logger from .fetchparams import expand_fetch_params -from .. import PROJECT_HOME +from .. import PROJECT_HOME, exceptions from ..utils import cached_property, expand_path from ..utils.compat import text_type @@ -23,10 +23,10 @@ def validate_section_name(name, section_type): invalid = set(name) - SECTION_NAME_CHARS if invalid: chars_display = ''.join(sorted(SECTION_NAME_CHARS)) - raise CliError('The {}-section "{}" contains invalid characters. Only ' - 'the following characters are allowed for storage and ' - 'pair names:\n{}'.format(section_type, name, - chars_display)) + raise exceptions.UserError( + 'The {}-section "{}" contains invalid characters. Only ' + 'the following characters are allowed for storage and ' + 'pair names:\n{}'.format(section_type, name, chars_display)) def _validate_general_section(general_config): @@ -48,9 +48,10 @@ def _validate_general_section(general_config): .format(u', '.join(missing))) if problems: - raise CliError(u'Invalid general section. Copy the example ' - u'config from the repository and edit it: {}' - .format(PROJECT_HOME), problems=problems) + raise exceptions.UserError( + u'Invalid general section. Copy the example ' + u'config from the repository and edit it: {}' + .format(PROJECT_HOME), problems=problems) def _validate_pair_section(pair_config): @@ -77,8 +78,10 @@ def load_config(): with open(fname) as f: general, pairs, storages = read_config(f) except Exception as e: - raise CliError('Error during reading config {}: {}' - .format(fname, e)) + raise exceptions.UserError( + 'Error during reading config {}: {}' + .format(fname, e) + ) return Config(general, pairs, storages) @@ -105,7 +108,8 @@ def read_config(f): def handle_general(_, options): if general: - raise CliError('More than one general section in config file.') + raise exceptions.UserError( + 'More than one general section in config file.') general.update(options) def bad_section(name, options): @@ -125,7 +129,8 @@ def read_config(f): f = handlers.get(section_type, bad_section) f(name, get_options(section)) except ValueError as e: - raise CliError('Section `{}`: {}'.format(section, str(e))) + raise exceptions.UserError( + 'Section `{}`: {}'.format(section, str(e))) _validate_general_section(general) if getattr(f, 'name', None): @@ -189,7 +194,7 @@ class Config(object): args = self.storages[storage_name] except KeyError: pair_pref = 'Pair {}: '.format(pair_name) if pair_name else '' - raise CliError( + raise exceptions.UserError( '{}Storage {!r} not found. ' 'These are the configured storages: {}' .format(pair_pref, storage_name, list(self.storages)) diff --git a/vdirsyncer/cli/tasks.py b/vdirsyncer/cli/tasks.py index 9ded076..4dca815 100644 --- a/vdirsyncer/cli/tasks.py +++ b/vdirsyncer/cli/tasks.py @@ -4,10 +4,11 @@ import functools import json from .config import CollectionConfig -from .utils import CliError, JobFailed, cli_logger, coerce_native, \ +from .utils import JobFailed, cli_logger, coerce_native, \ collections_for_pair, get_status_name, handle_cli_error, load_status, \ save_status, storage_class_from_config, storage_instance_from_config +from .. import exceptions from ..sync import sync from ..utils.compat import to_unicode @@ -28,7 +29,7 @@ def prepare_pair(wq, pair_name, collections, config, callback, **kwargs): try: config_a, config_b = all_collections[collection_name] except KeyError: - raise CliError( + raise exceptions.UserError( 'Pair {}: Collection {} not found. These are the ' 'configured collections:\n{}' .format(pair_name, @@ -98,8 +99,10 @@ def repair_collection(config, collection): if config['collection'] == collection: break else: - raise CliError('Couldn\'t find collection {} for storage {}.' - .format(collection, storage_name)) + raise exceptions.UserError( + 'Couldn\'t find collection {} for storage {}.' + .format(collection, storage_name) + ) config['type'] = storage_type storage = storage_instance_from_config(config) diff --git a/vdirsyncer/cli/utils.py b/vdirsyncer/cli/utils.py index 1ab007e..c7a9820 100644 --- a/vdirsyncer/cli/utils.py +++ b/vdirsyncer/cli/utils.py @@ -14,7 +14,7 @@ import click import click_threading -from . import CliError, cli_logger +from . import cli_logger from .. import DOCS_HOME, exceptions from ..sync import IdentConflict, StorageEmpty, SyncConflict from ..utils import expand_path, get_class_init_args @@ -78,7 +78,7 @@ def handle_cli_error(status_name=None): try: raise - except (CliError, exceptions.UserError) as e: + except exceptions.UserError as e: cli_logger.critical(e) except StorageEmpty as e: cli_logger.error( @@ -243,10 +243,11 @@ def _handle_collection_not_found(config, collection, e=None): except NotImplementedError as e: cli_logger.error(e) - raise CliError('Unable to find or create collection "{collection}" for ' - 'storage "{storage}". Please create the collection ' - 'yourself.'.format(collection=collection, - storage=storage_name)) + raise exceptions.UserError( + 'Unable to find or create collection "{collection}" for ' + 'storage "{storage}". Please create the collection ' + 'yourself.'.format(collection=collection, + storage=storage_name)) def _collections_for_pair_impl(status_path, pair): @@ -341,7 +342,8 @@ def storage_class_from_config(config): try: cls = storage_names[storage_name] except KeyError: - raise CliError('Unknown storage type: {}'.format(storage_name)) + raise exceptions.UserError( + 'Unknown storage type: {}'.format(storage_name)) return cls, config @@ -368,7 +370,7 @@ def storage_instance_from_config(config, create=True): def handle_storage_init_error(cls, config): e = sys.exc_info()[1] - if isinstance(e, (click.Abort, CliError, KeyboardInterrupt)): + if isinstance(e, (click.Abort, exceptions.UserError, KeyboardInterrupt)): raise all, required = get_class_init_args(cls) @@ -393,8 +395,10 @@ def handle_storage_init_error(cls, config): cli_logger.exception('') problems.append(str(e)) - raise CliError(u'Failed to initialize {}'.format(config['instance_name']), - problems=problems) + raise exceptions.UserError( + u'Failed to initialize {}'.format(config['instance_name']), + problems=problems + ) class WorkerQueue(object): diff --git a/vdirsyncer/exceptions.py b/vdirsyncer/exceptions.py index 6effc7f..1a58816 100644 --- a/vdirsyncer/exceptions.py +++ b/vdirsyncer/exceptions.py @@ -21,6 +21,15 @@ class UserError(Error, ValueError): '''Wrapper exception to be used to signify the traceback should not be shown to the user.''' + problems = None + + def __str__(self): + msg = Error.__str__(self) + for problem in self.problems or (): + msg += u'\n - {}'.format(problem) + + return msg + class CollectionNotFound(Error): '''Collection not found'''