Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion confuse/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,12 @@ def load(self) -> None:
elif self.filename is None:
raise TypeError("filename is required for YamlSource")
else:
value = yaml_util.load_yaml(self.filename, loader=self.loader) or {}
value = yaml_util.load_yaml(self.filename, loader=self.loader)
if value is None:
value = {}

if isinstance(value, dict):
self.clear()
Comment thread
Imkkey marked this conversation as resolved.
self.update(value)
else:
# We enforce that the loaded YAML is a mapping (dict)
Expand Down
8 changes: 5 additions & 3 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ Unreleased
New features
~~~~~~~~~~~~

..
Bug fixes
~~~~~~~~~
Bug fixes
~~~~~~~~~

- Remove stale keys when reloading YAML sources, allowing deleted overrides to
fall back to lower-priority sources.

..
For plugin developers
Expand Down
97 changes: 97 additions & 0 deletions test/test_yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,100 @@ def test_list_unchanged(self):
def test_invalid_yaml_string_unchanged(self):
v = confuse.yaml_util.parse_as_scalar("!", confuse.Loader)
assert v == "!"


class TestYamlReload:
def test_removed_keys_reveal_defaults(self, tmp_path):
path = tmp_path / "config.yaml"
path.write_text("foo: override\nremoved: old\nkept: before\n")
config = confuse.Configuration("test", read=False)
config.add({"foo": "default"})
config.set_file(str(path))
config.set({"memory": "preserved"})
foo = config["foo"]
assert foo.get() == "override"

path.write_text("kept: after\nadded: new\n")
config.reload()

assert foo.get() == "default"
assert not config["removed"].exists()
assert config["kept"].get() == "after"
assert config["added"].get() == "new"
assert config["memory"].get() == "preserved"

@pytest.mark.parametrize(
"contents", ["", "{}", "# Empty configuration\n", "null", "~"]
)
def test_empty_file_clears_source(self, tmp_path, contents):
path = tmp_path / "config.yaml"
path.write_text("foo: bar\n")
source = confuse.YamlSource(str(path))

path.write_text(contents)
source.load()

assert source == {}

def test_missing_optional_file_clears_source(self, tmp_path):
path = tmp_path / "config.yaml"
path.write_text("foo: bar\n")
source = confuse.YamlSource(str(path), optional=True)

path.unlink()
source.load()

assert source == {}
path.write_text("new: value\n")
source.load()
assert source == {"new": "value"}

@pytest.mark.parametrize(
"contents, error",
[
("foo: [", confuse.ConfigReadError),
("- item", TypeError),
("[]", TypeError),
("false", TypeError),
("0", TypeError),
("0.0", TypeError),
('""', TypeError),
],
)
def test_failed_reload_preserves_source(self, tmp_path, contents, error):
path = tmp_path / "config.yaml"
path.write_text("foo: bar\n")
source = confuse.YamlSource(str(path))

path.write_text(contents)
with pytest.raises(error):
source.load()

assert source == {"foo": "bar"}

def test_missing_required_file_preserves_source(self, tmp_path):
path = tmp_path / "config.yaml"
path.write_text("foo: bar\n")
source = confuse.YamlSource(str(path))

path.unlink()
with pytest.raises(confuse.ConfigReadError):
source.load()

assert source == {"foo": "bar"}

def test_reload_preserves_source_metadata_and_order(self, tmp_path):
path = tmp_path / "config.yaml"
path.write_text("first: before\nsecond: before\n")
source = confuse.YamlSource(str(path), default=True, base_for_paths=True)
config = confuse.Configuration("test", read=False)
config.add(source)

path.write_text("second: after\nfirst: after\n")
config.reload()

assert config.sources[0] is source
assert list(source) == ["second", "first"]
assert source.filename == str(path)
assert source.default is True
assert source.base_for_paths is True
Loading