def test_setattr_list_with_dict(): l = [{"b": True, "c": {"d": "thename"}}, "somestring", True] cfg = Config() # When we assign a list to the key # it remains an instance of list, or # whatever sequence type it was. cfg.a = l assert isinstance(cfg.a, list) # Internally though any Mapping objects # inside that list, no matter how nested, # got transformed into the Config objects. assert cfg.a == [ Config({ "b": True, "c": { "d": "thename" } }), "somestring", True, ] # And, again, we get all the nice accessibility # shenenigans as a result of such transformation. assert cfg.a[0].c.d == "thename"
def test_setattr_dict(): d = {"b": True, "c": {"d": "thename"}} cfg = Config() # Assign dict to the key. It should atuomatically # get transformed into the Config object. cfg.a = d assert dict(cfg.a) == d assert cfg.a == Config(d) # Notice how the dict got transformed into a Config # during assignment so that now we have all the "nice" # accessibility features of Config. assert cfg.a.c.d == "thename"
def test_setitem(): cfg = Config(NESTED, MERGABLE) cfg["a1"]["b1"]["c1"] = 6 assert cfg["a1"]["b1"]["c1"] == 6 assert cfg.a1.b1.c1 == 6 cfg = Config() cfg["f1"] = False assert cfg["f1"] is False cfg["f2"]["g3"]["h3"] = 123 assert cfg["f2.g3.h3"] == 123 cfg["u1.u4.u6"] = True assert cfg["u1"]["u4"]["u6"] is True
def test_nested_merge(): cfg = Config(NESTED, MERGABLE) # "val" has replaced 3 as value for a1.b1.c3 assert cfg.a1.b1.c3 == "val" # other values are still intact assert cfg.a1.b2.c3 == 1.1 assert cfg.a1.b1.c1 == 1
def test_lower(): config = Config() config.merge({"SOME_KeY": "VALuE"}) assert config.SOME_KeY == "VALuE" with pytest.raises(AssertionError): assert config.some_key == "VALuE" config.clear() config.merge({"SOME_KeY": "VALuE"}) config = config.lower() assert config.some_key == "VALuE" with pytest.raises(AssertionError): assert config.SOME_KeY == "VALuE"
def test_features(): from ilexconf import Config config = Config() config.a.b.c = True assert ( config.a.b.c == True ) assert ( config["a"]["b"]["c"] == True ) assert ( config["a.b.c"] == True ) assert ( config["a.b"].c == True ) assert ( config.a["b.c"] == True ) assert ( config.a["b"].c == True ) if True: config.a.b.c = [ "my_string", {"d": "nested_value"} ] assert ( config.a.b.c[0] == "my_string" ) assert ( config.a.b.c[1] == Config({"d": "nested_value"}) ) assert ( dict(config.a.b.c[1]) == {"d": "nested_value"} )
def test_nested_access(): cfg = Config(NESTED) # Plain access assert cfg["a1"]["b1"]["c1"] == 1 assert cfg["a1.b1.c1"] == 1 assert cfg.a1.b1.c1 == 1 # Mixed access assert cfg["a1"].b1.C2 == 2 assert cfg.a1["b1"].c3 == 3 assert cfg.a1.b2["c3"] == 1.1 # Direct access assert cfg.d1 is False assert cfg["d1"] is False
def test_upper(): config = Config() config.merge({"sOmE_kEy": True}) assert config.sOmE_kEy == True uppered = config.upper() assert uppered.SOME_KEY == True config.upper(inplace=True) assert config.SOME_KEY == True
def test_from_env_no_separator(): os.environ["AWS_DEFAULT_REGION"] = "us-east-1" config = from_env(prefix="_").lower(True) with pytest.raises(AssertionError): assert config.aws_default_region == "whatever" assert config.aws_default_region == Config() config = from_env(separator="").lower(True) with pytest.raises(AssertionError): assert config.AWS_DEFAULT_REGION == "us-east-1" assert config.aws_default_region == "us-east-1" config = from_env(separator=None) with pytest.raises(AssertionError): assert config.aws_default_region == "us-east-1" assert config.AWS_DEFAULT_REGION == "us-east-1" config = from_env() with pytest.raises(AssertionError): assert config.aws_default_region == "us-east-1" assert config.AWS_DEFAULT_REGION == "us-east-1"
from cleo import argument as cleo_argument from ilexconf import Config common_args = Config() def _add_arg(arg): common_args[arg.name].arg = arg def argument(name): return common_args[name].arg path = cleo_argument( name="path", description="[optional] Path to the configuration file.", optional=True, ) _add_arg(path)
def test_as_dict(): cfg = Config(NESTED, MERGABLE) d = dict(cfg) assert d == MERGED
def test_list(): cfg = Config(WITH_LISTS) assert dict(cfg) == WITH_LISTS
def test_setattr(): cfg = Config(NESTED, MERGABLE) cfg.a1.b1.c1 = 8 assert cfg.a1.b1.c1 == 8
def test_empty_config(): cfg = Config() assert cfg == {} assert "a0" not in cfg
def test_quick_start( settings_json_dict, settings_json_file_path, resulting_dict, tmp_path ): # os.putenv("AWS_DEFAULT_REGION", "us-east-1") os.environ["AWS_DEFAULT_REGION"] = "us-east-1" # [create] from ilexconf import Config, from_json, from_env, to_json # Empty config config = Config() assert dict(config) == {} # Create config from json and merge it into our initial config # Let settings_json_file_path = "settings.json" where inside the file we have # { "database": { "connection": { "host": "localhost", "port": 5432 } } } config.merge(from_json(settings_json_file_path)) assert dict(config) == { "database": {"connection": {"host": "localhost", "port": 5432}} } # Merge dict into config config.merge({"database": {"connection": {"host": "test.local"}}}) assert dict(config) == { "database": {"connection": {"host": "test.local", "port": 5432}} } # Merge environment variables into config config.merge(from_env(prefix="AWS_", separator="__").lower(inplace=True)) assert dict(config) == { "database": {"connection": {"host": "test.local", "port": 5432}}, "default_region": "us-east-1", } # Merge keyword arguments config.set("my__keyword__argument", True, key_sep="__") assert dict(config) == { "database": {"connection": {"host": "test.local", "port": 5432}}, "default_region": "us-east-1", "my": {"keyword": {"argument": True}}, } # Clear values, just like with dict config.clear() assert dict(config) == {} # Or, better yet, do this all in one step, since Config() constructor # accepts any number of mapping objects and keyword arguments as # initialization parameters. However, order of parameters matters. # Last mappings are merged on top of others. And keywords override even that. config = Config( from_json(settings_json_file_path), {"database": {"connection": {"host": "test.local"}}}, database__connection__port=4000, ) assert dict(config) == { "database": {"connection": {"host": "test.local", "port": 4000}} } # [create] # from ilexconf import ( # from_json, # # from_yaml, # # from_toml, # from_ini, # # from_python, # # from_dotenv, # from_env, # ) # [read] cfg1 = from_json(settings_json_file_path) assert dict(cfg1) == { "database": {"connection": {"host": "localhost", "port": 5432}} } # [read] # cfg2 = Config( # from_yaml("settings.yaml"), # from_toml("settings.toml") # ) # cfg3 = Config( # from_ini("settings.ini"), # from_python("settings.py"), # from_dotenv(".env"), # from_env() # ) # [access] # Classic way assert config["database"]["connection"]["host"] == "test.local" # Dotted key notation assert config["database.connection.host"] == "test.local" # Via attributes assert config.database.connection.host == "test.local" # Any combination of the above assert config["database"].connection.host == "test.local" assert config.database["connection.host"] == "test.local" assert config.database["connection"].host == "test.local" assert config.database.connection["host"] == "test.local" # [access] # [upsert] # Change value that already exists in the dictionary # just like you would do with simple dict config["database"]["connection"]["port"] = 8080 assert config["database"]["connection"]["port"] == 8080 # Create new value using 'dotted notation'. Notice that # 'user' field did not exist before. config["database.connection.user"] = "******" assert config["database.connection.user"] == "root" # Create new value using. 'password' field did not exist # before we assigned a value to it and was created automatically. config.database.connection.password = "******" assert config.database.connection.password == "secret stuff" # [upsert] # [merge] # Config correctly merges nested values. Notice how it overrides # the value of the 'password' key in the nested 'connection' config # from 'secret stuff' to 'different secret' config.database.connection.merge({"password": "******"}) assert config.database.connection.password == "different secret" # [merge] # [smart-merge] merged = Config( {"a1": {"c1": 1, "c2": 2, "c3": 3}}, {"a1": {"c3": "other"}} ) # Instead of overriding the value of the "a1" key completely, `merge` method # will recursively look inside and merge nested values. assert dict(merged) == {"a1": {"c1": 1, "c2": 2, "c3": "other"}} # [smart-merge] # [as-dict] assert dict(config) == { "database": { "connection": { "host": "test.local", "port": 8080, "user": "******", "password": "******", } } } # [as-dict] # [write] # Temporary path p = tmp_path / "settings.json" # Save config to_json(config, p) # Verify written file is correct assert dict(from_json(p)) == { "database": { "connection": { "host": "test.local", "port": 8080, "user": "******", "password": "******", } } } # [write] # [subclass] class MyConfig(Config): def __init__(self, do_stuff=False): # Initialize your custom config using json settings file super().__init__(from_json(settings_json_file_path)) # Add some custom value depending on some logic if do_stuff: # Here, we create new nested key that did not exist # before and assign a value to it. self.my.custom.key = "Yes, do stuff" # Merge one more mapping on top self.merge({"Horizon": "Up"}) # [subclass] # [test-subclass] # Now you can use your custom defined Config. Given the `setting.json` file that # contains { "database": { "connection": { "host": "localhost", "port": 5432 } } } # MyConfig will have the following values: config = MyConfig(do_stuff=True) assert dict(config) == { "database": { "connection": { "host": "localhost", "port": 5432, }, }, "Horizon": "Up", "my": {"custom": {"key": "Yes, do stuff"}}, }
from cleo import option as cleo_option from ilexconf import Config from ilexconf.adapters import enabled_formats common_opts = Config() def _add_option(o, choices=None): common_opts[o.long_name].opt = o if choices: common_opts[ o.long_name].opt._description += f"Choices: {', '.join(choices)}." common_opts[o.long_name].choices = choices def option(name): if name not in common_opts: raise Exception(f"Option {name} was not defined") return common_opts[name].opt filetype = cleo_option( long_name="type", short_name="t", description="File type. ", flag=False, value_required=True, ) _add_option(filetype, choices=enabled_formats)
def test_simple_merge(): cfg = Config(Config({"c1": 1, "C2": 2, "c3": 3}), Config({"c3": "val"})) assert dict(cfg) == {"c1": 1, "C2": 2, "c3": "val"}