From bec73245e9da898463aed553b2062b7f508a00dd Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Wed, 19 Jan 2022 20:39:03 -0800 Subject: [PATCH] Fix traceback when a YAML validation error occurs (#480, #482). --- NEWS | 3 +++ borgmatic/config/validate.py | 16 ++++++++------ setup.py | 2 +- tests/unit/config/test_validate.py | 34 ++++++++++++++---------------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/NEWS b/NEWS index db09762..2f35702 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +1.5.23.dev0 + * #480, #482: Fix traceback when a YAML validation error occurs. + 1.5.22 * #288: Add database dump hook for MongoDB. * #470: Move mysqldump options to the beginning of the command due to MySQL bug 30994. diff --git a/borgmatic/config/validate.py b/borgmatic/config/validate.py index bf3aced..3f8f303 100644 --- a/borgmatic/config/validate.py +++ b/borgmatic/config/validate.py @@ -15,7 +15,7 @@ def schema_filename(): return pkg_resources.resource_filename('borgmatic', 'config/schema.yaml') -def format_error_path_element(path_element): +def format_json_error_path_element(path_element): ''' Given a path element into a JSON data structure, format it for display as a string. ''' @@ -25,14 +25,14 @@ def format_error_path_element(path_element): return str('.{}'.format(path_element)) -def format_error(error): +def format_json_error(error): ''' Given an instance of jsonschema.exceptions.ValidationError, format it for display as a string. ''' if not error.path: return 'At the top level: {}'.format(error.message) - formatted_path = ''.join(format_error_path_element(element) for element in error.path) + formatted_path = ''.join(format_json_error_path_element(element) for element in error.path) return "At '{}': {}".format(formatted_path.lstrip('.'), error.message) @@ -44,8 +44,8 @@ class Validation_error(ValueError): def __init__(self, config_filename, errors): ''' - Given a configuration filename path and a sequence of - jsonschema.exceptions.ValidationError instances, create a Validation_error. + Given a configuration filename path and a sequence of string error messages, create a + Validation_error. ''' self.config_filename = config_filename self.errors = errors @@ -56,7 +56,7 @@ class Validation_error(ValueError): ''' return 'An error occurred while parsing a configuration file at {}:\n'.format( self.config_filename - ) + '\n'.join(format_error(error) for error in self.errors) + ) + '\n'.join(error for error in self.errors) def apply_logical_validation(config_filename, parsed_configuration): @@ -117,7 +117,9 @@ def parse_configuration(config_filename, schema_filename, overrides=None): validation_errors = tuple(validator.iter_errors(config)) if validation_errors: - raise Validation_error(config_filename, validation_errors) + raise Validation_error( + config_filename, tuple(format_json_error(error) for error in validation_errors) + ) apply_logical_validation(config_filename, config) diff --git a/setup.py b/setup.py index d7adce2..0a40c6f 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -VERSION = '1.5.22' +VERSION = '1.5.23.dev0' setup( diff --git a/tests/unit/config/test_validate.py b/tests/unit/config/test_validate.py index 0ab4c01..84eb0a8 100644 --- a/tests/unit/config/test_validate.py +++ b/tests/unit/config/test_validate.py @@ -4,33 +4,31 @@ from flexmock import flexmock from borgmatic.config import validate as module -def test_format_error_path_element_formats_array_index(): - module.format_error_path_element(3) == '[3]' +def test_format_json_error_path_element_formats_array_index(): + module.format_json_error_path_element(3) == '[3]' -def test_format_error_path_element_formats_property(): - module.format_error_path_element('foo') == '.foo' +def test_format_json_error_path_element_formats_property(): + module.format_json_error_path_element('foo') == '.foo' -def test_format_error_formats_error_including_path(): - flexmock(module).format_error_path_element = lambda element: '.{}'.format(element) +def test_format_json_error_formats_error_including_path(): + flexmock(module).format_json_error_path_element = lambda element: '.{}'.format(element) error = flexmock(message='oops', path=['foo', 'bar']) - assert module.format_error(error) == "At 'foo.bar': oops" + assert module.format_json_error(error) == "At 'foo.bar': oops" -def test_format_error_formats_error_without_path(): - flexmock(module).should_receive('format_error_path_element').never() +def test_format_json_error_formats_error_without_path(): + flexmock(module).should_receive('format_json_error_path_element').never() error = flexmock(message='oops', path=[]) - assert module.format_error(error) == 'At the top level: oops' + assert module.format_json_error(error) == 'At the top level: oops' -def test_validation_error_string_contains_error_messages_and_config_filename(): - flexmock(module).format_error = lambda error: error.message - error = module.Validation_error( - 'config.yaml', (flexmock(message='oops', path=None), flexmock(message='uh oh')) - ) +def test_validation_error_string_contains_errors(): + flexmock(module).format_json_error = lambda error: error.message + error = module.Validation_error('config.yaml', ('oops', 'uh oh')) result = str(error) @@ -40,7 +38,7 @@ def test_validation_error_string_contains_error_messages_and_config_filename(): def test_apply_logical_validation_raises_if_archive_name_format_present_without_prefix(): - flexmock(module).format_error = lambda error: error.message + flexmock(module).format_json_error = lambda error: error.message with pytest.raises(module.Validation_error): module.apply_logical_validation( @@ -53,7 +51,7 @@ def test_apply_logical_validation_raises_if_archive_name_format_present_without_ def test_apply_logical_validation_raises_if_archive_name_format_present_without_retention_prefix(): - flexmock(module).format_error = lambda error: error.message + flexmock(module).format_json_error = lambda error: error.message with pytest.raises(module.Validation_error): module.apply_logical_validation( @@ -67,7 +65,7 @@ def test_apply_logical_validation_raises_if_archive_name_format_present_without_ def test_apply_locical_validation_raises_if_unknown_repository_in_check_repositories(): - flexmock(module).format_error = lambda error: error.message + flexmock(module).format_json_error = lambda error: error.message with pytest.raises(module.Validation_error): module.apply_logical_validation(