def test_config_of_configs(): uploads = Config({ 'threads': 1, 'db': { 'user': '******', }, }) downloads = Config({ 'enabled': True, 'tmp_dir': '/tmp', }) uploads.threads.value = 5 downloads.tmp_dir.value = '/tmp/downloads' config = Config({ 'uploads': uploads, 'downloads': downloads, }) assert config.uploads.threads.value == 5 config.uploads.threads.value = 3 assert uploads.threads.value == 3 assert config.uploads.threads is uploads.threads assert config.downloads.enabled is downloads.enabled uploads.reset() assert config.uploads.threads.value == 1
def test_item_value_changed_hook_not_called_when_resetting_a_not_set(): config = Config({'a': Item()}) @config.hooks.item_value_changed def item_value_changed(item, old_value, new_value): raise AssertionError('This should not have been called') config.reset() config.a.value = not_set
def test_item_reset_is_tracked(): config = Config({'a': 'aaa'}) config.a.value = 'bbb' with config.changeset_context() as ctx: config.a.value = 'ccc' assert len(ctx.values) == 1 config.reset() assert len(ctx.values) == 1 assert ctx.values[config.a] is not_set
def test_reset_resets_values_to_defaults(): config = Config({'x': Item(type=int), 'y': Item(default='YES')}) config.x.value = 23 config.y.value = 'NO' assert config.x.value == 23 assert config.y.value == 'NO' config.reset() assert config.x.is_default assert config.y.value == 'YES'
def test_read_reads_multiple_files_in_order(tmpdir): m = Config({ 'a': { 'x': Item(type=float), 'y': 'aye', }, 'b': { 'm': False, 'n': Item(type=int), } }) path1 = tmpdir.join('config1.ini').strpath path2 = tmpdir.join('config2.ini').strpath path3 = tmpdir.join('config3.ini').strpath # Empty file m.configparser.dump(path1) # Can load from one empty file m.configparser.load(path1) assert m.is_default m.a.x.value = 0.33 m.b.n.value = 42 m.configparser.dump(path2) # Can load from one non-empty file m.reset() m.configparser.load(path2) assert not m.is_default assert m.a.x.value == 0.33 m.reset() m.a.x.value = 0.66 m.b.m.value = 'YES' m.configparser.dump(path3) m.reset() m.configparser.load([path1, path2, path3]) assert m.a.x.value == 0.66 assert m.a.y.is_default assert m.b.m.value is True assert m.b.m.raw_str_value == 'YES' assert m.b.n.value == 42 m.reset() m.configparser.load([path3, path2, path1]) assert m.a.x.value == 0.33 # this is the only difference with the above order assert m.a.y.is_default assert m.b.m.value is True assert m.b.m.raw_str_value == 'YES' assert m.b.n.value == 42 # Make sure multiple paths not supported in non-list syntax. with pytest.raises(TypeError): m.configparser.load(path3, path2, path1)
def test_item_value_changed_hook(): config = Config({'uploads': { 'db': { 'user': '******', } }}) calls = [] @config.hooks.item_value_changed def item_value_changed(old_value=None, new_value=None, item=None, **kwargs): calls.append((item, old_value, new_value)) assert calls == [] config.reset() assert calls == [] config.uploads.db.user.set('admin') assert len(calls) == 1 assert calls[-1] == (config.uploads.db.user, not_set, 'admin') config.uploads.db.user.value = 'Administrator' assert len(calls) == 2 assert calls[-1] == (config.uploads.db.user, 'admin', 'Administrator') config.load_values({'uploads': {'something_nonexistent': True}}) assert len(calls) == 2 config.load_values({'uploads': { 'db': { 'user': '******' } }}, as_defaults=True) assert len(calls) == 2 config.load_values({'uploads': {'db': {'user': '******'}}}) assert len(calls) == 3 assert calls[-1] == (config.uploads.db.user, 'Administrator', 'NEW VALUE')
def test_item_value_changed_hook_called_on_item_reset(): config = Config({'a': 'aaa', 'b': 'bbb', 'c': Item()}) calls = [] @config.hooks.item_value_changed def item_value_changed(item, old_value, new_value): calls.append(item.name) assert len(calls) == 0 config.reset() assert len(calls) == 0 # Setting same value as default value triggers the event config.a.value = 'aaa' assert calls == ['a'] # Setting same value as the custom value before triggers the event config.a.value = 'aaa' assert calls == ['a', 'a'] # Actual reset config.reset() assert calls == ['a', 'a', 'a']
def test_tracking_of_tricky_change_and_no_change_situations(): config = Config({'a': 'aaa'}) # Set custom value that matches the default. Should be tracked. with config.changeset_context() as ctx: assert len(ctx.values) == 0 config.a.value = 'aaa' assert len(ctx.values) == 1 config.reset() assert len(ctx.values) == 0 # No custom value set to start with with config.changeset_context() as ctx: assert len(ctx.values) == 0 # Set custom value config.a.value = 'bbb' assert len(ctx.values) == 1 assert ctx.values[config.a] == 'bbb' # Set custom value which matches default value -- it still is a change. config.a.value = 'aaa' assert len(ctx.values) == 1 assert ctx.values[config.a] == 'aaa' config.reset() assert len(ctx.values) == 0 # Have a custom value to start with config.a.value = 'ccc' with config.changeset_context() as ctx: # Make one change config.a.value = 'ddd' assert len(ctx.values) == 1 assert ctx.values[config.a] == 'ddd' # Change back to the original value (within this context), should result in no changes config.a.value = 'ccc' assert len(ctx.values) == 0 # Reset still results in a change within this context. config.reset() assert len(ctx.values) == 1 assert ctx.values[config.a] == not_set
def test_nested_config(): """ This demonstrates how an application config can be created from multiple sections (which in turn can be created from others). """ # Declaration of a config section may be a plain dictionary db_config = { 'host': 'localhost', 'user': '******', 'password': '******', } # Or, it may be an already functional instance of Config server_config = Config({ 'port': 8080, }) # # All these sections can be combined into one config: # config = Config({ 'db': db_config, 'server': server_config, 'greeting': 'Hello', # and you can have plain config items next to sections }) # You can load values assert config.greeting.value == 'Hello' # Your original schemas are safe -- db_config dictionary won't be changed config.db.user.value = 'root' assert config.db.user.value == 'root' assert db_config['user'] == 'admin' # You can also change values by reading them from a dictionary. # Unknown names will be ignored unless you pass as_defaults=True # but in that case you will overwrite any previously existing items. config.load_values({ 'greeting': 'Good morning!', 'comments': { 'enabled': False } }) assert config.greeting.value == 'Good morning!' assert 'comments' not in config # You can check if config value is the default value assert not config.db.user.is_default assert config.server.port.is_default # Or if it has any value at all assert config.server.port.has_value # Iterate over all items (recursively) all = dict(config.iter_items(recursive=True)) assert all[('db', 'host')] is config.db.host assert all[('server', 'port')] is config.server.port # Export all values config_dict = config.dump_values() assert config_dict['db'] == { 'host': 'localhost', 'user': '******', 'password': '******' } # Each section is a Config instance too, so you can export those separately too: assert config.server.dump_values() == config_dict['server'] # You can reset individual items to their default values assert config.db.user.value == 'root' config.db.user.reset() assert config.db.user.value == 'admin' # Or sections config.db.user.value = 'root_again' assert config.db.user.value == 'root_again' config.db.reset() assert config.db.user.value == 'admin' # Or you can reset all configuration and you can make sure all values match defaults assert not config.is_default config.reset() assert config.is_default