diff --git a/borgmatic/borg/create.py b/borgmatic/borg/create.py index 54a7f9ad..ccdb41a9 100644 --- a/borgmatic/borg/create.py +++ b/borgmatic/borg/create.py @@ -196,7 +196,7 @@ def check_all_root_patterns_exist(patterns): if missing_paths: raise ValueError( - f"Source directories / root pattern paths do not exist: {', '.join(missing_paths)}" + f"Source directories or root pattern paths do not exist: {', '.join(missing_paths)}" ) diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 140dc1ae..7ad600d9 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -1,7 +1,6 @@ import collections import io import itertools -import json import re import sys from argparse import ArgumentParser @@ -377,7 +376,7 @@ def add_array_element_arguments(arguments_group, unparsed_arguments, flag_name): registry_name for registry_name, action_type in arguments_group._registries['action'].items() # Not using isinstance() here because we only want an exact match—no parent classes. - if type(argument_action) == action_type + if type(argument_action) is action_type ) except StopIteration: return @@ -492,7 +491,7 @@ def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names # As a UX nicety, allow boolean options that have a default of false to have command-line flags # without values. - if schema_type == 'boolean' and schema.get('default') == False: + if schema_type == 'boolean' and schema.get('default') is False: arguments_group.add_argument( full_flag_name, action='store_true', diff --git a/borgmatic/config/schema.py b/borgmatic/config/schema.py index 6981dde5..3d13c0c2 100644 --- a/borgmatic/config/schema.py +++ b/borgmatic/config/schema.py @@ -23,6 +23,16 @@ def get_properties(schema): return schema.get('properties', {}) +SCHEMA_TYPE_TO_PYTHON_TYPE = { + 'array': list, + 'boolean': bool, + 'integer': int, + 'number': decimal.Decimal, + 'object': dict, + 'string': str, +} + + def parse_type(schema_type, **overrides): ''' Given a schema type as a string, return the corresponding Python type. @@ -34,14 +44,7 @@ def parse_type(schema_type, **overrides): ''' try: return dict( - { - 'array': list, - 'boolean': bool, - 'integer': int, - 'number': decimal.Decimal, - 'object': dict, - 'string': str, - }, + SCHEMA_TYPE_TO_PYTHON_TYPE, **overrides, )[schema_type] except KeyError: @@ -54,7 +57,8 @@ def compare_types(schema_type, target_types, match=any): target type strings, return whether every schema type is in the set of target types. If the schema type is a list of strings, use the given match function (such as any or all) to - compare elements. + compare elements. For instance, if match is given as all, then every element of the schema_type + list must be in the target types. ''' if isinstance(schema_type, list): if match(element_schema_type in target_types for element_schema_type in schema_type): diff --git a/borgmatic/config/validate.py b/borgmatic/config/validate.py index 29215f91..98bf080c 100644 --- a/borgmatic/config/validate.py +++ b/borgmatic/config/validate.py @@ -21,7 +21,7 @@ def schema_filename(): return schema_path -def load_schema(schema_path): +def load_schema(schema_path): # pragma: no cover ''' Given a schema filename path, load the schema and return it as a dict. diff --git a/tests/integration/commands/test_arguments.py b/tests/integration/commands/test_arguments.py index 032b9495..ed6a9892 100644 --- a/tests/integration/commands/test_arguments.py +++ b/tests/integration/commands/test_arguments.py @@ -14,7 +14,12 @@ def test_make_argument_description_with_array_adds_example(): }, flag_name='flag', ) - == 'Thing. Example value: "[1, \'- foo\', bar: baz]"' + # Apparently different versions of ruamel.yaml serialize this + # differently. + in ( + 'Thing. Example value: "[1, \'- foo\', bar: baz]"' + 'Thing. Example value: "[1, \'- foo\', {bar: baz}]"' + ) ) diff --git a/tests/integration/config/test_arguments.py b/tests/integration/config/test_arguments.py index 449964ee..9bcc37ff 100644 --- a/tests/integration/config/test_arguments.py +++ b/tests/integration/config/test_arguments.py @@ -1,5 +1,4 @@ import pytest -from flexmock import flexmock from borgmatic.config import arguments as module diff --git a/tests/unit/config/test_arguments.py b/tests/unit/config/test_arguments.py index 25c8873f..18ad7f56 100644 --- a/tests/unit/config/test_arguments.py +++ b/tests/unit/config/test_arguments.py @@ -175,9 +175,7 @@ def test_prepare_arguments_for_config_skips_option_with_none_value(): 'other_option': {'type': 'string'}, }, }, - ) == ( - (('other_option',), 'value2'), - ) + ) == ((('other_option',), 'value2'),) def test_prepare_arguments_for_config_skips_option_missing_from_schema(): @@ -190,6 +188,4 @@ def test_prepare_arguments_for_config_skips_option_missing_from_schema(): 'other_option': {'type': 'string'}, }, }, - ) == ( - (('other_option',), 'value2'), - ) + ) == ((('other_option',), 'value2'),) diff --git a/tests/unit/config/test_generate.py b/tests/unit/config/test_generate.py index 885bdcca..ad90bb5a 100644 --- a/tests/unit/config/test_generate.py +++ b/tests/unit/config/test_generate.py @@ -2,7 +2,6 @@ import pytest from flexmock import flexmock from borgmatic.config import generate as module -import borgmatic.config.schema def test_schema_to_sample_configuration_generates_config_map_with_examples(): diff --git a/tests/unit/config/test_normalize.py b/tests/unit/config/test_normalize.py index abd7e54d..29fe25e8 100644 --- a/tests/unit/config/test_normalize.py +++ b/tests/unit/config/test_normalize.py @@ -359,6 +359,11 @@ def test_normalize_commands_moves_individual_command_hooks_to_unified_commands( {'repositories': [{'path': '/repo', 'label': 'foo'}]}, False, ), + ( + {'repositories': [{'path': None, 'label': 'foo'}]}, + {'repositories': []}, + False, + ), ( {'prefix': 'foo'}, {'prefix': 'foo'}, diff --git a/tests/unit/config/test_schema.py b/tests/unit/config/test_schema.py index a07b19ba..8af890fb 100644 --- a/tests/unit/config/test_schema.py +++ b/tests/unit/config/test_schema.py @@ -1,3 +1,5 @@ +import pytest + from borgmatic.config import schema as module @@ -75,3 +77,84 @@ def test_get_properties_interleaves_oneof_list_properties(): ('field3', {'example': 'Example 3'}), ] ) + + +def test_parse_type_maps_schema_type_to_python_type(): + module.parse_type('boolean') == bool + + +def test_parse_type_with_unknown_schema_type_raises(): + with pytest.raises(ValueError): + module.parse_type('what') + + +def test_parse_type_respect_overrides_when_mapping_types(): + module.parse_type('boolean', boolean=int) == int + + +@pytest.mark.parametrize( + 'schema_type,target_types,match,expected_result', + ( + ( + 'string', + {'integer', 'string', 'boolean'}, + None, + True, + ), + ( + 'string', + {'integer', 'boolean'}, + None, + False, + ), + ( + 'string', + {'integer', 'string', 'boolean'}, + all, + True, + ), + ( + 'string', + {'integer', 'boolean'}, + all, + False, + ), + ( + ['string', 'array'], + {'integer', 'string', 'boolean'}, + None, + True, + ), + ( + ['string', 'array'], + {'integer', 'boolean'}, + None, + False, + ), + ( + ['string', 'array'], + {'integer', 'string', 'boolean', 'array'}, + all, + True, + ), + ( + ['string', 'array'], + {'integer', 'string', 'boolean'}, + all, + False, + ), + ( + ['string', 'array'], + {'integer', 'boolean'}, + all, + False, + ), + ), +) +def test_compare_types_returns_whether_schema_type_matches_target_types( + schema_type, target_types, match, expected_result +): + if match: + assert module.compare_types(schema_type, target_types, match) == expected_result + else: + assert module.compare_types(schema_type, target_types) == expected_result