From 1e43a21903927da12e5645ae480c6e7813581df1 Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Sat, 12 Sep 2020 15:22:03 -0500 Subject: [PATCH] Use dicts instead of dots in the internal settings representation --- src/pgwui_server/__init__.py | 115 +++++++++------ tests/test___init__.py | 266 ++++++++++++++++++++++------------- 2 files changed, 236 insertions(+), 145 deletions(-) diff --git a/src/pgwui_server/__init__.py b/src/pgwui_server/__init__.py index 529693b..fdc930f 100644 --- a/src/pgwui_server/__init__.py +++ b/src/pgwui_server/__init__.py @@ -53,41 +53,58 @@ log = logging.getLogger(__name__) # Functions -def abort_on_bad_setting( - errors, component_keys, component_checkers, key, settings): - '''Abort on a bad pgwui setting +def dot_to_component_settings(settings, key, component): + '''Put a component's settings into its own dict, + adding to what's already there ''' - if key[:6] == 'pgwui.': - if key in component_keys: - component = component_keys[key] - if component in component_checkers: - errors.extend( - component_checkers[component](settings.get(key, {}))) - else: - if key[6:] not in SETTINGS: - errors.append(common_ex.UnknownSettingKeyError(key)) + comp_settings = settings['pgwui'].setdefault(component, dict()) + comp_settings.update(settings[key]) + del settings[key] -def require_setting(errors, setting, settings): - if setting not in settings: - errors.append(common_ex.MissingSettingError(setting)) +def component_setting_into_dict( + errors, component_checkers, key, settings, component): + '''Put a component's settings in its own dict and validate them + ''' + comp_settings = dot_to_component_settings(settings, key, component) + if component in component_checkers: + errors.extend( + component_checkers[component](comp_settings)) + + +def dot_to_dict(settings, key, new_key): + settings['pgwui'][new_key] = settings[key] + del settings[key] + + +def key_to_ini(key): + '''Convert the setting key to a key used in an ini file's declaration + ''' + return 'pgwui.{}'.format(key) -def boolean_setting(errors, setting, settings): - if setting in settings: +def require_setting(errors, setting, pgwui_settings): + if setting not in pgwui_settings: + errors.append(common_ex.MissingSettingError(key_to_ini(setting))) + + +def boolean_setting(errors, setting, pgwui_settings): + if setting in pgwui_settings: try: - val = literal_eval(settings[setting]) + val = literal_eval(pgwui_settings[setting]) except ValueError: val = None if (val is not True and val is not False): errors.append(common_ex.NotBooleanSettingError( - setting, settings[setting])) + key_to_ini(setting), pgwui_settings[setting])) def validate_setting_values(errors, settings): '''Check each settings value for validity ''' + pgwui_settings = settings['pgwui'] + # pg_host can be missing, it defaults to the Unix socket (in psycopg2) # pg_port can be missing, it defaults to 5432 (in psycopg2) @@ -95,22 +112,23 @@ def validate_setting_values(errors, settings): # default_db can be missing, then the user sees no default # dry_run - require_setting(errors, 'pgwui.dry_run', settings) - boolean_setting(errors, 'pgwui.dry_run', settings) + require_setting(errors, 'dry_run', pgwui_settings) + boolean_setting(errors, 'dry_run', pgwui_settings) # route_prefix can be missing, defaults to no route prefix which is fine. # routes can be missing, sensible defaults are built-in. # validate_hmac - boolean_setting(errors, 'pgwui.validate_hmac', settings) + boolean_setting(errors, 'validate_hmac', pgwui_settings) def do_validate_hmac(settings): '''True unless the user has specificly rejected hmac validation ''' - return ('pgwui.validate_hmac' not in settings - or literal_eval(settings['pgwui.validate_hmac'])) + pgwui_settings = settings['pgwui'] + return ('validate_hmac' not in pgwui_settings + or literal_eval(pgwui_settings['validate_hmac'])) def validate_hmac(errors, settings): @@ -142,27 +160,33 @@ def parse_assignments(lines): return result -def parse_component_settings(component_keys, key, settings): - '''Parse plugin component setting assigments into a dict +def setting_into_dict( + errors, components, component_checkers, key, settings): + '''Separate a pgwui setting into a dict on '.' chars; validate + component settings. ''' - if key in component_keys: - settings[key] = dict(parse_assignments(settings[key])) - - -def map_keys_to_components(components): - return dict([(plugin.component_to_key(component), component) - for component in components]) + if key[:6] == 'pgwui.': + new_key = key[6:] + if new_key in components: + settings[key] = dict(parse_assignments(settings[key])) + component_setting_into_dict( + errors, component_checkers, key, settings, new_key) + else: + if new_key in SETTINGS: + dot_to_dict(settings, key, new_key) + else: + errors.append(common_ex.UnknownSettingKeyError(key)) -def validate_settings(errors, settings, components): - '''Be sure all settings validate +def dictify_settings(errors, settings, components): + '''Convert . in the pgwui settings to dict mappings, and validate + the result. ''' - component_keys = map_keys_to_components(components) component_checkers = plugin.find_pgwui_check_settings() - for key in settings.keys(): - parse_component_settings(component_keys, key, settings) - abort_on_bad_setting( - errors, component_keys, component_checkers, key, settings) + settings.setdefault('pgwui', dict()) + for key in list(settings.keys()): + setting_into_dict( + errors, components, component_checkers, key, settings) validate_setting_values(errors, settings) validate_hmac(errors, settings) @@ -186,7 +210,7 @@ def exit_on_invalid_settings(settings, components): '''Exit when settings don't validate ''' errors = [] - validate_settings(errors, settings, components) + dictify_settings(errors, settings, components) if errors: exit_reporting_errors(errors) @@ -194,8 +218,9 @@ def exit_on_invalid_settings(settings, components): def add_routes(config, settings): '''Add routes found in pgwui.routes setting ''' - if 'pgwui.routes' in settings: - routes = parse_assignments(settings['pgwui.routes']) + pgwui_settings = settings['pgwui'] + if 'routes' in pgwui_settings: + routes = parse_assignments(pgwui_settings['routes']) for name, route in routes: config.add_route(name, route) @@ -203,7 +228,7 @@ def add_routes(config, settings): def autoconfigurable_components(settings, components): '''Automatic pgwui component discovery ''' - autoconfig = settings.get('pgwui.autoconfigure', True) + autoconfig = settings['pgwui'].get('autoconfigure', True) if not autoconfig: return [] @@ -218,7 +243,7 @@ def apply_component_defaults(settings, components): ''' components_to_config = autoconfigurable_components(settings, components) - rp = settings.get('pgwui.route_prefix') + rp = settings['pgwui'].get('route_prefix') with Configurator(settings=settings, route_prefix=rp) as config: config.include('pgwui_common') for component in components_to_config: diff --git a/tests/test___init__.py b/tests/test___init__.py index edce6cc..6f786f6 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -24,7 +24,6 @@ import copy import logging import pytest import sys -import unittest.mock import pyramid.testing @@ -71,75 +70,118 @@ mock_find_pgwui_components = testing.make_mock_fixture( pgwui_common.plugin, 'find_pgwui_components') mock_find_pgwui_check_settings = testing.make_mock_fixture( pgwui_common.plugin, 'find_pgwui_check_settings') -mock_component_to_key = testing.make_mock_fixture( - pgwui_common.plugin, 'component_to_key') # Unit tests -# abort_on_bad_setting() +# dot_to_component_settings() -def test_abort_on_bad_setting_unknown(): - '''Nothing bad happens when there's a non-pgwui setting''' - errors = [] - pgwui_server_init.abort_on_bad_setting(errors, {}, {}, 'foo', {}) +def test_dot_to_component_settings_new(): + '''Adds a new dict and puts the settings in it + ''' + comp_settings = {'foo': 'foo', 'bar': 'bar'} + component = 'pgwui_component' + key = 'pgwui.' + component + settings = {'pgwui': {}, + key: comp_settings} + expected = {'pgwui': {component: comp_settings}} - assert errors == [] + pgwui_server_init.dot_to_component_settings( + settings, key, 'pgwui_component') + assert settings == expected -def test_abort_on_bad_setting_bad(): - '''Delivers an error on a bad pgwui setting''' - errors = [] - pgwui_server_init.abort_on_bad_setting( - errors, {}, {}, 'pgwui.foo', {}) - assert errors - assert isinstance(errors[0], common_ex.UnknownSettingKeyError) +def test_dot_to_component_settings_old(): + '''Extends an existing dict in the settings + ''' + comp_settings = {'foo': 'foo', 'bar': 'bar'} + component = 'pgwui_component' + key = 'pgwui.' + component + settings = {'pgwui': {component: {'foo': 'bar', 'baz': 'baz'}}, + key: comp_settings} + expected = {'pgwui': + {component: {'foo': 'foo', 'bar': 'bar', 'baz': 'baz'}}} + pgwui_server_init.dot_to_component_settings( + settings, key, 'pgwui_component') -def test_abort_on_bad_setting_good(): - '''Does nothing when a known pgwui setting is supplied''' - errors = [] - pgwui_server_init.abort_on_bad_setting( - errors, {}, {}, 'pgwui.pg_host', {}) + assert settings == expected - assert errors == [] + +mock_dot_to_component_setting = testing.make_mock_fixture( + pgwui_server_init, 'dot_to_component_settings') -def test_abort_on_bad_setting_plugin_no_config(): - '''Does nothing when a known plugin has a setting and there is no - config for the plugin''' +# component_setting_into_dict() + +def test_component_setting_into_dict_no_checker( + mock_dot_to_component_setting): + '''When there's no checker nothing is done + ''' errors = [] - pgwui_server_init.abort_on_bad_setting( - errors, {'pgwui.pgwui_upload': None}, {}, 'pgwui.pgwui_upload', {}) + + pgwui_server_init.component_setting_into_dict( + errors, {}, 'pgwui.pgwui_component', None, 'pgwui_component') assert errors == [] -def test_abort_on_bad_setting_plugin_config(): - '''Calls the component checker with the component's config - and appends the result of the call to the errors. +def test_component_setting_into_dict_checker( + mock_dot_to_component_setting): + '''When there's a checker its result is appended to the errors + ''' + errors = ['someerror'] + new_errors = ['new1', 'new2'] + expected = copy.deepcopy(errors) + expected.extend(new_errors) + + pgwui_server_init.component_setting_into_dict( + errors, {'pgwui_component': lambda settings: new_errors}, + 'pgwui.pgwui_component', None, 'pgwui_component') + + assert errors == expected + + +mock_component_setting_into_dict = testing.make_mock_fixture( + pgwui_server_init, 'component_setting_into_dict') + + +# dot_to_dict() + +def test_dot_to_dict(): + '''Removes pgwui.* settings, replaces them with a dict entry ''' - orig_errors = ['old', 'errors'] - new_errors = ['some', 'errors'] - component = 'test component' - sample_key = 'pgwui.pgwui_upload' - sample_config = {sample_key: 'some sample config'} + settings = {'foo': 1, + 'pgwui': {}, + 'pgwui.abc': 'abc', + 'pgwui.def': 'def'} + expected = {'foo': 1, + 'pgwui': {'abc': 'abc'}, + 'pgwui.def': 'def'} - errors = copy.deepcopy(orig_errors) - mock_checker = unittest.mock.Mock(side_effect=lambda *args: new_errors) + pgwui_server_init.dot_to_dict(settings, 'pgwui.abc', 'abc') - pgwui_server_init.abort_on_bad_setting( - errors, {sample_key: component}, {component: mock_checker}, - sample_key, sample_config) + assert settings == expected - mock_checker.assert_called_once - assert list(mock_checker.call_args[0]) == [sample_config[sample_key]] - assert errors == orig_errors + new_errors +mock_dot_to_dict = testing.make_mock_fixture( + pgwui_server_init, 'dot_to_dict') -mock_abort_on_bad_setting = testing.make_mock_fixture( - pgwui_server_init, 'abort_on_bad_setting') + +# key_to_ini() + +def test_key_to_ini(): + '''The return value is as expected + ''' + key = 'pgwui_example' + result = pgwui_server_init.key_to_ini(key) + + assert result == 'pgwui.' + key + + +mock_key_to_ini = testing.make_mock_fixture( + pgwui_server_init, 'key_to_ini') # require_setting() @@ -220,7 +262,7 @@ mock_boolean_setting = testing.make_mock_fixture( def test_validate_setting_values(mock_require_setting, mock_boolean_setting): '''Calls require_setting() and boolean_setting()''' - pgwui_server_init.validate_setting_values([], {}) + pgwui_server_init.validate_setting_values([], {'pgwui': {}}) assert mock_require_setting.called assert mock_boolean_setting.called @@ -234,20 +276,20 @@ mock_validate_setting_values = testing.make_mock_fixture( def test_do_validate_hmac_none(): '''pgwui.validate_hmac defaults to True''' - assert pgwui_server_init.do_validate_hmac({}) is True + assert pgwui_server_init.do_validate_hmac({'pgwui': {}}) is True def test_do_validate_hmac_True(): '''Require hmac validation when pgwui.validate_hmac is True''' result = pgwui_server_init.do_validate_hmac( - {'pgwui.validate_hmac': 'True'}) + {'pgwui': {'validate_hmac': 'True'}}) assert result is True def test_do_validate_hmac_False(): '''No hmac validation when pgwui.validate_hmac is False''' result = pgwui_server_init.do_validate_hmac( - {'pgwui.validate_hmac': 'False'}) + {'pgwui': {'validate_hmac': 'False'}}) assert result is False @@ -332,74 +374,96 @@ mock_parse_assignments = testing.make_mock_fixture( pgwui_server_init, 'parse_assignments') -# parse_component_settings() +# setting_into_dict() + +def test_setting_into_dict_unknown( + mock_parse_assignments, + mock_component_setting_into_dict, + mock_dot_to_dict): + '''No new errors when there's a non-pgwui setting''' + errors = [] + pgwui_server_init.setting_into_dict(errors, [], {}, 'foo', {}) + + assert errors == [] -def test_parse_component_settings(mock_parse_assignments): - '''Plugin component settings are properly parsed - ''' - expected_server_settings = {'a': '1', 'b': '2'} - component = 'pgwui.pgwui_server' - settings = {component: 'ignored'} - mock_parse_assignments.return_value = [('a', '1'), ('b', '2')] - pgwui_server_init.parse_component_settings( - [component], component, settings) +def test_setting_into_dict_bad( + mock_parse_assignments, + mock_component_setting_into_dict, + mock_dot_to_dict): + '''Delivers an error on a bad pgwui setting''' + errors = [] - assert settings[component] == expected_server_settings + pgwui_server_init.setting_into_dict( + errors, [], {}, 'pgwui.foo', {}) + assert errors + assert isinstance(errors[0], common_ex.UnknownSettingKeyError) -mock_parse_component_settings = testing.make_mock_fixture( - pgwui_server_init, 'parse_component_settings') +def test_setting_into_dict_good( + mock_parse_assignments, + mock_component_setting_into_dict, + mock_dot_to_dict): + '''Calls dot_to_dict when a known pgwui setting is supplied''' + errors = [] -# map_keys_to_components() + pgwui_server_init.setting_into_dict( + errors, [], {}, 'pgwui.pg_host', {}) -def test_map_keys_to_components(mock_component_to_key): - '''Returns expected result + mock_dot_to_dict.assert_called_once() + assert errors == [] + + +def test_setting_into_dict_plugin_component( + mock_parse_assignments, + mock_component_setting_into_dict, + mock_dot_to_dict): + '''When a setting is for a component the setting is parsed and + moved into a dict ''' - components = ['a', 'b', 'c'] - mock_component_to_key.side_effect = ['keya', 'keyb', 'keyc'] + key = 'pgwui.pgwui_component' + settings = {key: None} + errors = [] + mock_parse_assignments.return_value = {} - result = pgwui_server_init.map_keys_to_components(components) + pgwui_server_init.setting_into_dict( + errors, ['pgwui_component'], {}, key, settings) - assert result == {'keya': 'a', 'keyb': 'b', 'keyc': 'c'} + mock_parse_assignments.assert_called_once() + mock_component_setting_into_dict.assert_called_once() + assert errors == [] -mock_map_keys_to_components = testing.make_mock_fixture( - pgwui_server_init, 'map_keys_to_components') +mock_setting_into_dict = testing.make_mock_fixture( + pgwui_server_init, 'setting_into_dict') -# validate_settings() +# dictify_settings() -def test_validate_settings(mock_map_keys_to_components, - mock_parse_component_settings, - mock_find_pgwui_check_settings, - mock_abort_on_bad_setting, - mock_validate_setting_values, - mock_validate_hmac): - '''Calls abort_on_bad_setting() for each key in setting, - with the proper list of config declaration names for the - plugin components +def test_dictify_settings(mock_find_pgwui_check_settings, + mock_setting_into_dict, + mock_validate_setting_values, + mock_validate_hmac): + '''Calls setting_into_dict() for each key in setting, + with the proper list of plugin components ''' settings = {'key1': 'value1', 'key2': 'value2'} components = ['pgwui_server'] - key_map = {'pgwui.server': 'anentrypoint'} - - mock_map_keys_to_components.side_effect = lambda *args: key_map errors = [] - pgwui_server_init.validate_settings(errors, settings, components) + pgwui_server_init.dictify_settings(errors, settings, components) assert mock_validate_setting_values.called assert mock_validate_hmac.called - assert mock_abort_on_bad_setting.call_count == len(settings) - assert mock_abort_on_bad_setting.call_args[0][1] == key_map + assert mock_setting_into_dict.call_count == len(settings) + assert mock_setting_into_dict.call_args[0][1] == components -mock_validate_settings = testing.make_mock_fixture( - pgwui_server_init, 'validate_settings') +mock_dictify_settings = testing.make_mock_fixture( + pgwui_server_init, 'dictify_settings') # exit_reporting_errors() @@ -466,21 +530,21 @@ mock_exit_reporting_errors = testing.make_mock_fixture( def test_exit_on_invalid_settings_invalid(monkeypatch, mock_exit_reporting_errors): - '''Calls validate_settings and exit_reporting_errors() when + '''Calls dictify_settings and exit_reporting_errors() when setting is invalid ''' - def mock_validate_settings(errors, settings, components): + def mock_dictify_settings(errors, settings, components): errors.append('error1') - monkeypatch.setattr(pgwui_server_init, 'validate_settings', - mock_validate_settings) + monkeypatch.setattr(pgwui_server_init, 'dictify_settings', + mock_dictify_settings) pgwui_server_init.exit_on_invalid_settings({}, []) assert mock_exit_reporting_errors.called -def test_exit_on_invalid_settings_valid(mock_validate_settings): +def test_exit_on_invalid_settings_valid(mock_dictify_settings): '''Returns, without exiting, when all settings are valid ''' pgwui_server_init.exit_on_invalid_settings({}, []) @@ -500,7 +564,7 @@ def test_autoconfiguable_components_no_autoconfig(): test_components = ['some', 'components'] result = pgwui_server_init.autoconfigurable_components( - {'pgwui.autoconfigure': False}, test_components) + {'pgwui': {'autoconfigure': False}}, test_components) assert result == [] @@ -511,7 +575,9 @@ def test_autoconfigurable_components_log_info(caplog): caplog.set_level(logging.INFO) pgwui_server_init.autoconfigurable_components( - {'pgwui.autoconfigure': True, 'pyramid.include': None}, []) + {'pgwui': {'autoconfigure': True}, + 'pyramid.include': None}, + []) logs = caplog.record_tuples @@ -527,7 +593,7 @@ def test_autoconfigurable_components_components_returned(): test_components = ['some', 'components'] result = pgwui_server_init.autoconfigurable_components( - {'pgwui.autoconfigure': True}, test_components) + {'pgwui': {'pgwui.autoconfigure': True}}, test_components) assert result == test_components @@ -542,7 +608,7 @@ def test_add_routes_empty(mock_add_route): '''When there is no pgwui.routes setting nothing gets added''' with pyramid.testing.testConfig() as config: mocked_add_route = mock_add_route(config) - pgwui_server_init.add_routes(config, {}) + pgwui_server_init.add_routes(config, {'pgwui': {}}) assert not mocked_add_route.called @@ -555,7 +621,7 @@ def test_add_routes_notempty(mock_add_route, mock_parse_assignments): mock_parse_assignments.return_value = test_routes with pyramid.testing.testConfig() as config: mocked_add_route = mock_add_route(config) - pgwui_server_init.add_routes(config, {'pgwui.routes': ''}) + pgwui_server_init.add_routes(config, {'pgwui': {'routes': ''}}) assert mocked_add_route.call_count == len(test_routes) @@ -580,7 +646,7 @@ def test_apply_component_defaults(monkeypatch, caplog, monkeypatch.setattr(pgwui_server_init, 'Configurator', MockConfigurator) - result = pgwui_server_init.apply_component_defaults({}, []) + result = pgwui_server_init.apply_component_defaults({'pgwui': {}}, []) assert isinstance(result, MockConfigurator) logs = caplog.record_tuples -- 2.34.1