def test_subconfig_invalid(config_data):
    config_data["subconfigs"]["a"]["str_field"] = 42
    lookup = Lookup(config_data)

    with pytest.raises(ConfigError,
                       match=r"subconfigs/a/str_field must be a string"):
        for name, sublookup in lookup.iterate_objects("subconfigs"):
            SubConfig(sublookup)
def test_str_field_optional():
    class Config(BaseConfig):
        str_field: Optional[str] = None

    lookup = Lookup({})
    assert Config(lookup).str_field is None
    lookup = Lookup({"str_field": "foo"})
    assert Config(lookup).str_field == "foo"
def test_trailing_slash():
    class Config(BaseConfig):
        str_field: str = configfield(force_trailing_slash=True)

    lookup = Lookup({"str_field": "ab"})
    assert Config(lookup).str_field == "ab/"

    lookup = Lookup({"str_field": "ab/"})
    assert Config(lookup).str_field == "ab/"
def test_timedelta_optional():
    class Config(BaseConfig):
        timedelta_field: Optional[timedelta] = None

    lookup = Lookup({})
    assert Config(lookup).timedelta_field is None
    lookup = Lookup({"timedelta_field": "42s"})
    timedelta_field = Config(lookup).timedelta_field
    assert timedelta_field is not None
    assert timedelta_field.total_seconds() == 42
def test_str_dict_field_invalid(config_data):
    config_data["str_dict_field"] = "foo"
    lookup = Lookup(config_data)
    with raises(ConfigError,
                match=r"str_dict_field must be a mapping with string values"):
        Config(lookup)

    config_data["str_dict_field"] = {"foo": 1}
    lookup = Lookup(config_data)
    with raises(ConfigError,
                match=r"str_dict_field must be a mapping with string values"):
        Config(lookup)
def test_str_list_field_invalid(config_data):
    config_data["str_list_field"] = "foo"
    lookup = Lookup(config_data)
    with raises(ConfigError,
                match=r"str_list_field must be a list of strings"):
        Config(lookup)

    config_data["str_list_field"] = [1, 2, 3]
    lookup = Lookup(config_data)
    with raises(ConfigError,
                match=r"str_list_field must be a list of strings"):
        Config(lookup)
def test_basic(config_data):
    lookup = Lookup(config_data)
    config = Config(lookup)

    config.subconfigs = {}
    for name, sublookup in lookup.iterate_objects("subconfigs"):
        config.subconfigs[name] = SubConfig(sublookup)

    assert config.str_field == "foo"
    assert config.int_field == 42
    assert config.bool_field is False
    assert config.str_list_field == ["foo", "bar", "baz"]
    assert config.str_dict_field == {"foo": "bar", "baz": "wow"}
    assert config.timedelta_field.total_seconds() == 42
    assert config.subconfig.str_field == "baz"
    assert config.subconfigs["a"].str_field == "a"
def test_field_defaults():
    class Config(BaseConfig):
        str_field: str = "foo_default"

    lookup = Lookup({})
    config = Config(lookup)

    assert config.str_field == "foo_default"
def test_environment_variable_missing(config_data):
    if "FOO" in os.environ:
        del os.environ["FOO"]
    config_data["str_field"] = "<${FOO}>"
    lookup = Lookup(config_data)
    with raises(SubstitutionError,
                match=r'environment variable FOO is not set'):
        Config(lookup)
def test_config_field():
    class Config(BaseConfig):
        str_field: str = configfield(default="foo_default")
        skip_field: str = configfield(skip=True)

    lookup = Lookup({})
    config = Config(lookup)

    assert config.str_field == "foo_default"
def test_timedelta_field_invalid(config_data):
    config_data["timedelta_field"] = 100
    lookup = Lookup(config_data)
    with raises(
            ConfigError,
            match=
            r"timedelta_field should be a time interval of the form \<digits\>\[dhms\]"
    ):
        Config(lookup)
def test_bad_field_type():
    class Config(BaseConfig):
        set_field: set

    lookup = Lookup({})
    with raises(
            RuntimeError,
            match=r"Don't know how to handle config field of type <class 'set'>"
    ):
        Config(lookup)
def test_environment_variable_default(config_data):
    if "FOO" in os.environ:
        del os.environ["FOO"]
    config_data["str_field"] = "<${FOO:default}>"
    lookup = Lookup(config_data)
    assert Config(lookup).str_field == "<default>"
def test_environment_variable(config_data):
    config_data["str_field"] = "<${FOO}>"
    os.environ["FOO"] = 'foo'
    lookup = Lookup(config_data)
    assert Config(lookup).str_field == "<foo>"
def test_iterate_objects_none():
    lookup = Lookup({})
    result = [x for x in lookup.iterate_objects("not_there")]
    assert result == []
def test_missing_field(config_data):
    del config_data["str_field"]
    lookup = Lookup(config_data)
    with raises(ConfigError, match=r"A value is required for str_field"):
        Config(lookup)
def test_timedelta_default():
    class Config(BaseConfig):
        timedelta_field: timedelta = configfield(default=timedelta(days=1))

    lookup = Lookup({})
    assert Config(lookup).timedelta_field == timedelta(days=1)
def test_timedelta_formats(input, expected_seconds):
    class Config(BaseConfig):
        timedelta_field: timedelta = configfield(force_suffix=False)

    lookup = Lookup({"timedelta_field": input})
    assert Config(lookup).timedelta_field.total_seconds() == expected_seconds
def test_str_field_invalid(config_data):
    config_data["str_field"] = 42
    lookup = Lookup(config_data)
    with raises(ConfigError, match=r"str_field must be a string"):
        Config(lookup)
def test_bool_field_invalid(config_data):
    config_data["bool_field"] = "foo"
    lookup = Lookup(config_data)
    with raises(ConfigError, match=r"bool_field must be a boolean"):
        Config(lookup)
def test_int_field_invalid(config_data):
    config_data["int_field"] = "foo"
    lookup = Lookup(config_data)
    with raises(ConfigError, match=r"int_field must be an integer"):
        Config(lookup)