def test_config_of_configs(): uploads = Config({ 'threads': 1, 'enabled': True, 'api': { 'port': 8000, } }) downloads = Config({ 'tmp_dir': '/tmp', 'db': { 'user': '******', } }) config = Config({ 'uploads': uploads, 'downloads': downloads, 'messages': { 'greeting': 'Hello', } }) assert config.uploads.threads.is_item assert config.uploads.api.port.is_item assert config.downloads.db.user.is_item assert config.messages.greeting.is_item
def cx(): return Config([ ('with_', Item(name='with')), ('for', Config([ ('continue_', Item(name='continue')), ])), ])
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_config_of_configs(): config = Config({ 'uploads': Config({ 'a': 1, 'b': True, 'c': 'ccc', }), 'downloads': Config({ 'd': { 'e': 'eee', }, 'f': 'fff', }), }) with config.changeset_context() as ctx: config.uploads.a.value = 2 config.downloads.d.e.value = 'EEE' config.downloads.f.value = 'FFF' assert len(ctx.values) == 3 assert ctx.values[config.uploads.a] == 2 assert ctx.values[config.downloads.d.e] == 'EEE' assert ctx.values[config.downloads.f] == 'FFF' assert not config.is_default assert config.uploads.a.value == 2 assert config.downloads.d.e.value == 'EEE' assert config.downloads.f.value == 'FFF' ctx.reset() assert config.is_default
def test_json_read_and_write(defaults_json_path, user_json_path): c1 = Config() c1.json.load(defaults_json_path, as_defaults=True) c2 = Config() c2.json.load([defaults_json_path], as_defaults=True) c3 = Config() with open(defaults_json_path) as f: c3.json.load(f, as_defaults=True) assert c1.dump_values(with_defaults=False) == {} assert c1.dump_values(with_defaults=True) == { 'uploads': { 'threads': 1, 'enabled': False, 'tmp_dir': '/tmp', } } assert c1.dump_values() == c2.dump_values() == c3.dump_values() c1.json.load(user_json_path) c2.json.load([user_json_path]) with open(user_json_path) as f: c3.json.load(f) assert c1.dump_values(with_defaults=False) == { 'uploads': { 'threads': 5, 'enabled': True, } } assert c1.dump_values() == c2.dump_values() == c3.dump_values() updates = { 'uploads': { 'threads': 10, 'enabled': False, } } c1.load_values(updates) c2.load_values(updates) c3.load_values(updates) assert c1.dump_values() == c2.dump_values() == c3.dump_values() c1.json.dump(user_json_path) c2.json.load(user_json_path) assert c1.dump_values() == c2.dump_values() == c3.dump_values() assert c1.dump_values() == c2.dump_values() == c3.dump_values() with open(user_json_path, 'w') as f: c2.json.dump(f) c1.json.load(user_json_path) assert c1.dump_values() == c2.dump_values() == c3.dump_values()
def test_section_knows_its_alias(): config = Config() config.uploads = Config({'enabled': True}) assert config.uploads.alias == 'uploads' config.uploads.db = Config({'connection': {'user': '******'}}) assert config.uploads.db.alias == 'db' assert config.uploads.db.connection.alias == 'connection'
def mixed_app_config(raw_logging_config, raw_db_config): """ A config that contains a previously existing config as well as one generated on initialisation. """ return Config({ 'logging': Config(raw_logging_config), 'db': raw_db_config, })
def test_assigning_nameless_item_directly_to_config_should_set_its_name(): config = Config() config.dummy = Config() config.dummy.x = Item(value=5) assert config.dummy.x.name == 'x' config.dummy['y'] = Item(default=True) assert config.dummy.y.name == 'y' assert config.dump_values() == {'dummy': {'x': 5, 'y': True}}
def test_can_use__setitem__to_create_new_deep_paths(): config = Config() config['uploads'] = Config({'enabled': True}) with pytest.raises(TypeError): config['uploads', 'threads'] = 5 config['uploads', 'threads'] = Item(value=5) assert config.uploads.threads.type == Types.int config['uploads', 'db'] = Config({'user': '******'}) assert config.uploads.db
def config(): return Config(schema={ 'uploads': { 'enabled': False, 'threads': 1, 'db': Config({ 'user': '******', 'password': '******', }), }, 'greeting': 'Hello', })
def test_hooks_work_across_nested_configs(): config = Config({ 'a': Config({ 'aa': Config({ 'aaa': 'aaa-default', }), 'ab': { 'aba': 'aba-default', }, 'ac': 'ac-default', }), 'b': { 'ba': Config({ 'baa': 'baa-default', }), 'bb': { 'bba': 'bba-default', }, 'bc': 'bc-default', }, 'c': 'c-default', }) calls = [] @config.hooks.item_value_changed def item_value_changed(item): calls.append(('root', '.'.join(item.get_path()))) assert len(calls) == 0 config.c.value = 'c-1' assert len(calls) == 1 config.a.ac.value = 'ac-1' assert len(calls) == 2 config.a.aa.aaa.value = 'aaa-1' assert len(calls) == 3 config.a.ab.aba.value = 'aba-1' assert len(calls) == 4 config.b.bc.value = 'bc-1' assert len(calls) == 5 config.b.ba.baa.value = 'baa-1' assert len(calls) == 6 config.b.bb.bba.value = 'bba-1' assert len(calls) == 7
def c4(): return Config([ ('greeting', 'Hello!'), ('uploads', Config([ ('enabled', True), ('db', Config([('host', 'localhost'), ('user', 'root')])), ])), ('downloads', Config([ ('enabled', True), ('threads', 5), ])), ])
def test_config_item_value_can_be_unicode_str(tmpdir): config1 = Config({'greeting': u'Hello, {name}', 'name': u'Anonymous'}) config1.name.value = u'Jānis Bērziņš' assert config1.name.type == Types.str path = tmpdir.join('config.ini').strpath config1.configparser.dump(path, with_defaults=True) config2 = Config({'greeting': '', 'name': ''}) config2.configparser.load(path) assert config2.name.value == u'Jānis Bērziņš' assert config1.dump_values(with_defaults=True) == config2.dump_values( with_defaults=True)
def test_to_dict_should_not_include_items_with_no_usable_value(): config = Config() assert config.dump_values() == {} config.a = Item() config.b = Item() config.dummies = Config({'x': Item(), 'y': Item()}) assert config.dump_values() == {} config.dummies.x.value = 'yes' assert config.dump_values() == {'dummies': {'x': 'yes'}} config.b.value = 'no' assert config.dump_values() == {'dummies': {'x': 'yes'}, 'b': 'no'}
def test_accepts_configmanager_settings_which_are_passed_to_all_subsections(): configmanager_settings = { 'message': 'everyone should know this', } config1 = Config(configmanager_settings=configmanager_settings) assert config1.settings.message == 'everyone should know this' config2 = Config({'greeting': 'Hello'}, configmanager_settings=configmanager_settings) assert config2.settings.message == 'everyone should know this' config3 = Config({'db': {'user': '******'}}, configmanager_settings=configmanager_settings) assert config3.settings.message == 'everyone should know this' assert config3.db.settings.message == 'everyone should know this' assert config3.db.settings is config3.settings
def test_change_is_a_merge_of_all_changes_in_context(): config = Config({ 'a': 1, }) config.a.set('2') with config.changeset_context() as ctx: config.a.set(3) config.a.set(4) config.a.set('5') assert ctx.changes[config.a].old_value == 2 assert ctx.changes[config.a].old_raw_str_value == '2' assert ctx.changes[config.a].new_value == 5 assert ctx.changes[config.a].new_raw_str_value == '5' config.a.set(6) assert ctx.changes[config.a].old_value == 2 assert ctx.changes[config.a].old_raw_str_value == '2' assert ctx.changes[config.a].new_value == 6 assert ctx.changes[config.a].new_raw_str_value is not_set ctx.reset() assert config.a not in ctx.changes
def test_can_have_a_dict_as_a_config_value_if_wrapped_inside_item(): # You may want to have a dictionary as a config value if you only # change it all together or you only pass it all in one piece. config = Config({ 'db': { 'user': '******', 'password': '******', }, 'aws': Item(default={ 'access_key': '123', 'secret_key': 'secret', }) }) assert isinstance(config.aws, Item) assert config.aws.name == 'aws' with pytest.raises(AttributeError): assert config.aws.access_key.value == '123' assert config.aws.value['access_key'] == '123' # This should have no effect because it is working on a copy of the default # value, not the real thing. config.aws.value['secret_key'] = 'NEW_SECRET' assert config.dump_values()['aws'] == { 'access_key': '123', 'secret_key': 'secret' }
def test_adding_new_attributes_to_item(): # This demonstrates two ways of adding new attributes: # 1) by declaring ItemAttribute, 2) by creating them in initialiser class CustomItem(Item): help = ItemAttribute(name='help', default='No help available!') def __init__(self, *args, **kwargs): description = kwargs.pop('description', None) super(CustomItem, self).__init__(*args, **kwargs) self.description = description config = Config({ 'a': { 'b': 1, 'c': True, }, 'd': 'efg', }, item_factory=CustomItem) assert config.a.b.help assert config.a.b.description is None assert config.d.help assert config.d.description is None
def test_hooks_arent_handled_if_hooks_enabled_setting_is_set_to_falsey_value(): config = Config({'uploads': {'db': {'user': '******'}}}) calls = [] @config.hooks.item_value_changed def item_value_changed(**kwargs): calls.append(1) config.uploads.db.user.value = 'admin1' assert len(calls) == 1 config.uploads.db.user.value = 'admin2' assert len(calls) == 2 config.settings.hooks_enabled = False config.uploads.db.user.value = 'admin3' assert len(calls) == 2 config.settings.hooks_enabled = None config.uploads.db.user.value = 'admin4' assert len(calls) == 2 config.settings.hooks_enabled = True config.uploads.db.user.value = 'admin5' assert len(calls) == 3
def test_resets_single_item_changes(): config = Config({'a': 'aaa', 'b': 'bbb', 'c': 'ccc'}) config.c.value = 'CCC' assert config.a.raw_str_value is not_set assert config.c.raw_str_value == 'CCC' with config.changeset_context() as ctx: config.a.value = 'AAA' config.b.value = 'BBB' config.c.value = 'seeseesee' assert config.c.raw_str_value == 'seeseesee' assert len(ctx.values) == 3 ctx.reset(config.a) assert len(ctx.values) == 2 assert ctx.values[config.b] == 'BBB' assert ctx.values[config.c] == 'seeseesee' assert config.a.value == 'aaa' assert config.b.value == 'BBB' assert config.c.value == 'seeseesee' ctx.reset(config.c) assert config.a.value == 'aaa' assert config.b.value == 'BBB' assert config.c.value == 'CCC' assert config.a.raw_str_value is not_set assert config.c.raw_str_value == 'CCC'
def test_configparser_integration(tmpdir): defaults_ini = tmpdir.join('defaults.ini') defaults_ini.write('') defaults_ini_path = defaults_ini.strpath custom_ini = tmpdir.join('custom.ini') custom_ini.write('') custom_ini_path = custom_ini.strpath # Config sections expose ConfigParser adapter as configparser property: config = Config() # assuming that defaults.ini exists, this would initialise Config # with all values mentioned in defaults.ini set as defaults. # Just like with ConfigParser, this won't fail if the file does not exist. config.configparser.load(defaults_ini_path, as_defaults=True) # if you have already declared defaults, you can load custom # configuration without specifying as_defaults=True: config.configparser.load(custom_ini_path) # when you are done setting config values, you can write them to a file. config.configparser.dump(custom_ini_path) # Note that default values won't be written unless you explicitly request it # by passing with_defaults=True config.configparser.dump(custom_ini_path, with_defaults=True)
def test_dict_with_all_keys_prefixed_with_at_symbol_is_treated_as_item_meta(): config = Config({ 'enabled': { '@default': False, }, 'db': { 'user': '******', 'name': { '@help': 'Name of the database' }, '@help': 'db configuration', # This should be ignored for now }, }) paths = set(path for path, _ in config.iter_items(recursive=True, key='str_path')) assert 'enabled' in paths assert 'enabled.@default' not in paths assert 'db' not in paths assert 'db.@help' not in paths assert 'db.user' in paths assert 'db.name' in paths assert 'db.name.@help' not in paths assert config.enabled.default is False assert config.db.name.help == 'Name of the database' # Make sure it still behaves like a bool config.enabled.value = 'yes' assert config.enabled.value is True # Name is excluded because it has no value assert config.db.dump_values() == {'user': '******'} config.db.name.value = 'testdb' assert config.db.dump_values() == {'user': '******', 'name': 'testdb'}
def test_read_as_defaults_treats_all_values_as_schemas(tmpdir): path = tmpdir.join('conf.ini').strpath with open(path, 'w') as f: f.write('[uploads]\nthreads = 5\nenabled = no\n') f.write('[messages]\ngreeting = Hello, home!\n') m = Config() m.configparser.load(path, as_defaults=True) assert m.uploads assert m.uploads.threads.value == '5' assert m.uploads.enabled.value == 'no' with pytest.raises(NotFound): assert m.uploads.something_else assert m.messages.greeting.value == 'Hello, home!' # Reading again with as_defaults=True should not change the values, only the defaults m.uploads.threads.value = '55' m.configparser.load(path, as_defaults=True) assert m.uploads.threads.value == '55' assert m.uploads.threads.default == '5' # But reading with as_defaults=False should change the value m.configparser.load(path) assert m.uploads.threads.value == '5' assert m.uploads.threads.default == '5'
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_config_written_to_and_read_from_yaml_string(): config_str = ( 'uploads:\n' ' enabled: True\n' ' threads: 5\n' ' db:\n' ' user: root\n\n' ) config = Config() config.yaml.loads(config_str, as_defaults=True) # Make sure the order is preserved assert list(config.iter_paths(recursive=True, key='str_path')) == [ 'uploads', 'uploads.enabled', 'uploads.threads', 'uploads.db', 'uploads.db.user' ] # And the values assert config.dump_values() == { 'uploads': { 'enabled': True, 'threads': 5, 'db': { 'user': '******', } } } config_str2 = config.yaml.dumps(with_defaults=True) # TODO Fix this in Python 2 if six.PY3: assert config_str2 == ( 'uploads:\n' ' enabled: true\n' ' threads: 5\n' ' db:\n' ' user: root\n' ) config2 = Config() config2.yaml.loads(config_str2, as_defaults=True) assert list(config2.iter_paths(recursive=True, key='str_path')) == [ 'uploads', 'uploads.enabled', 'uploads.threads', 'uploads.db', 'uploads.db.user' ] assert config2.dump_values() == config.dump_values()
def test_schema_parser_does_not_modify_config(raw_logging_config): logging_config = Config(raw_logging_config) assert isinstance(logging_config, Config) assert logging_config['version'] assert logging_config['formatters'] config = Config({'logging': logging_config}) assert isinstance(config, Config) assert config['logging']['version'] assert config['logging']['formatters'] assert config['logging']['formatters'] is logging_config['formatters'] logging_config['version'].value = '2' assert config['logging']['version'].value == 2
def test_empty_dict_is_an_item_with_dict_type(): config = Config({ 'uploads': {} }) assert config.uploads.is_item assert config.uploads.default == {} assert config.uploads.type == Types.dict
def test_default_value_is_deep_copied(): things = [1, 2, 3, 4] config = Config({'items': things}) assert config['items'].value == [1, 2, 3, 4] things.remove(2) assert config['items'].value == [1, 2, 3, 4]
def test_standard_exceptions_raised_for_unknown_attributes_of_item(): config = Config({'greeting': 'Hello'}) with pytest.raises(AttributeError): _ = config.greeting.something with pytest.raises(TypeError): _ = config.greeting['something']
def test_must_not_allow_item_or_section_names_with_str_path_separator_in_them( ): with pytest.raises(ValueError): Config({ 'a.b': True, }) Config({'a.b': True}, str_path_separator='/') with pytest.raises(ValueError): Config({'a/b': True}, str_path_separator='/') # Sections with pytest.raises(ValueError): Config({'a.b': Section()}) with pytest.raises(ValueError): Config({'a/b': Section()}, str_path_separator='/') config = Config({'a/b': Section()}, str_path_separator='.') assert config['a/b'].is_section config = Config({'a.b': Section()}, str_path_separator='/') assert config['a.b'].is_section