From 57721937a3729ce8e1b6cbb1edcbbf85eb2b8a7a Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Sun, 23 Mar 2025 11:24:36 -0700 Subject: [PATCH] Factor out schema type comparion in config generation and get several tests passing (#303). --- borgmatic/commands/arguments.py | 1 + borgmatic/config/generate.py | 12 ++++-------- borgmatic/config/schema.py | 22 ++++++++++++++++++++++ tests/unit/config/test_generate.py | 28 ++++++++++++++++++++-------- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 32249f8f..2d285f50 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -297,6 +297,7 @@ def make_argument_description(schema, flag_name): an example or additional information as appropriate based on its type. Return the updated description for use in a command-line argument. ''' + # FIXME: Argument descriptions are apparently broken right now. description = schema.get('description') schema_type = schema.get('type') diff --git a/borgmatic/config/generate.py b/borgmatic/config/generate.py index 53f20133..58360ad0 100644 --- a/borgmatic/config/generate.py +++ b/borgmatic/config/generate.py @@ -36,10 +36,10 @@ def schema_to_sample_configuration(schema, source_config=None, level=0, parent_i schema_type = schema.get('type') example = schema.get('example') - if schema_type == 'array' or (isinstance(schema_type, list) and 'array' in schema_type): + if borgmatic.config.schema.compare_types(schema_type, {'array'}): config = ruamel.yaml.comments.CommentedSeq( example - if schema['items'].get('type') in SCALAR_SCHEMA_TYPES + if borgmatic.config.schema.compare_types(schema['items'].get('type'), SCALAR_SCHEMA_TYPES) else [ schema_to_sample_configuration( schema['items'], source_config, level, parent_is_sequence=True @@ -47,7 +47,7 @@ def schema_to_sample_configuration(schema, source_config=None, level=0, parent_i ] ) add_comments_to_configuration_sequence(config, schema, indent=(level * INDENT)) - elif schema_type == 'object' or (isinstance(schema_type, list) and 'object' in schema_type): + elif borgmatic.config.schema.compare_types(schema_type, {'object'}): if source_config and isinstance(source_config, list) and isinstance(source_config[0], dict): source_config = dict(collections.ChainMap(*source_config)) @@ -66,11 +66,7 @@ def schema_to_sample_configuration(schema, source_config=None, level=0, parent_i add_comments_to_configuration_object( config, schema, source_config, indent=indent, skip_first=parent_is_sequence ) - elif isinstance(schema_type, list) and all( - element_schema_type in SCALAR_SCHEMA_TYPES for element_schema_type in schema_type - ): - return example - elif schema_type in SCALAR_SCHEMA_TYPES: + elif borgmatic.config.schema.compare_types(schema_type, SCALAR_SCHEMA_TYPES, match=all): return example else: raise ValueError(f'Schema at level {level} is unsupported: {schema}') diff --git a/borgmatic/config/schema.py b/borgmatic/config/schema.py index 4442010f..d4698c3a 100644 --- a/borgmatic/config/schema.py +++ b/borgmatic/config/schema.py @@ -39,3 +39,25 @@ def parse_type(schema_type): }[schema_type] except KeyError: raise ValueError(f'Unknown type in configuration schema: {schema_type}') + + +def compare_types(schema_type, target_types, match=any): + ''' + Given a schema type as a string or a list of strings (representing multiple types) and a set of + 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. + ''' + if isinstance(schema_type, list): + if match( + element_schema_type in target_types for element_schema_type in schema_type + ): + return True + + return False + + if schema_type in target_types: + return True + + return False diff --git a/tests/unit/config/test_generate.py b/tests/unit/config/test_generate.py index abb520fb..9c07001d 100644 --- a/tests/unit/config/test_generate.py +++ b/tests/unit/config/test_generate.py @@ -2,6 +2,7 @@ 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(): @@ -9,13 +10,16 @@ def test_schema_to_sample_configuration_generates_config_map_with_examples(): 'type': 'object', 'properties': dict( [ - ('field1', {'example': 'Example 1'}), - ('field2', {'example': 'Example 2'}), - ('field3', {'example': 'Example 3'}), + ('field1', {'type': 'string', 'example': 'Example 1'}), + ('field2', {'type': 'string', 'example': 'Example 2'}), + ('field3', {'type': 'string', 'example': 'Example 3'}), ] ), } - flexmock(module).should_receive('get_properties').and_return(schema['properties']) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').and_return(False) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('object', {'object'}).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('string', module.SCALAR_SCHEMA_TYPES, match=all).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('get_properties').and_return(schema['properties']) flexmock(module.ruamel.yaml.comments).should_receive('CommentedMap').replace_with(dict) flexmock(module).should_receive('add_comments_to_configuration_object') @@ -46,11 +50,15 @@ def test_schema_to_sample_configuration_generates_config_sequence_of_maps_with_e 'items': { 'type': 'object', 'properties': dict( - [('field1', {'example': 'Example 1'}), ('field2', {'example': 'Example 2'})] + [('field1', {'type': 'string', 'example': 'Example 1'}), ('field2', {'type': 'string', 'example': 'Example 2'})] ), }, } - flexmock(module).should_receive('get_properties').and_return(schema['items']['properties']) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').and_return(False) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('array', {'array'}).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('object', {'object'}).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('string', module.SCALAR_SCHEMA_TYPES, match=all).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('get_properties').and_return(schema['items']['properties']) flexmock(module.ruamel.yaml.comments).should_receive('CommentedSeq').replace_with(list) flexmock(module).should_receive('add_comments_to_configuration_sequence') flexmock(module).should_receive('add_comments_to_configuration_object') @@ -66,11 +74,15 @@ def test_schema_to_sample_configuration_generates_config_sequence_of_maps_with_m 'items': { 'type': ['object', 'null'], 'properties': dict( - [('field1', {'example': 'Example 1'}), ('field2', {'example': 'Example 2'})] + [('field1', {'type': 'string', 'example': 'Example 1'}), ('field2', {'type': 'string', 'example': 'Example 2'})] ), }, } - flexmock(module).should_receive('get_properties').and_return(schema['items']['properties']) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').and_return(False) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('array', {'array'}).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args(['object', 'null'], {'object'}).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('compare_types').with_args('string', module.SCALAR_SCHEMA_TYPES, match=all).and_return(True) + flexmock(module.borgmatic.config.schema).should_receive('get_properties').and_return(schema['items']['properties']) flexmock(module.ruamel.yaml.comments).should_receive('CommentedSeq').replace_with(list) flexmock(module).should_receive('add_comments_to_configuration_sequence') flexmock(module).should_receive('add_comments_to_configuration_object')