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
2 changes: 1 addition & 1 deletion confuse/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def build_dict(
save_to = save_to[child_key]

# Save
if key in save_to:
if isinstance(save_to.get(key), dict) and isinstance(result, dict):
save_to[key].update(result)
else:
save_to[key] = result
Expand Down
10 changes: 7 additions & 3 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ Unreleased
New features
~~~~~~~~~~~~

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

- Fix nested environment and command-line overrides colliding with values in
parent mappings. More-specific values replace scalar, list, or null values;
mappings continue to merge. `#215
<https://github.com/beetbox/confuse/issues/215>`_

..
For plugin developers
Expand Down
31 changes: 31 additions & 0 deletions test/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,34 @@ def test_with_sep(self):
def test_with_prefix_and_sep(self):
self.config.set_env(prefix="MYAPP_", sep="_sep_")
assert self.config.get() == {"qux": {"nested": "d"}}


@pytest.mark.parametrize("reverse", [False, True])
@pytest.mark.parametrize(
"original",
["5432", "localhost", "[5432, 5434]", "null", "{fallback: 5432}"],
)
@pytest.mark.parametrize(
"override, expected",
[
("5433", 5433),
("remote", "remote"),
("[5433, 5435]", [5433, 5435]),
("null", None),
("{primary: 5433}", {"primary": 5433}),
],
)
def test_yaml_nested_leaf_override(original, override, expected, reverse):
entries = [
("TEST_DATABASE", f"{{host: localhost, port: {original}}}"),
("TEST_DATABASE__PORT", override),
]
if reverse:
entries.reverse()
environment = dict(entries)
with patch.dict("os.environ", environment, clear=True):
source = confuse.EnvSource("TEST_", parse_yaml_docs=True)
assert dict(os.environ) == environment
if original == "{fallback: 5432}" and isinstance(expected, dict):
expected = {"fallback": 5432, **expected}
assert source["database"] == {"host": "localhost", "port": expected}
48 changes: 48 additions & 0 deletions test/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
from argparse import Namespace
from collections import OrderedDict
from copy import deepcopy
from typing import Any

import pytest
Expand Down Expand Up @@ -81,3 +82,50 @@ def test_keep_none_with_nested(self):

result = confuse.util.build_dict(config.copy(), keep_none=True)
assert None is result["foo"]["bar"]


@pytest.mark.parametrize("reverse", [False, True])
@pytest.mark.parametrize("namespace", [False, True])
@pytest.mark.parametrize("original", [5432, "local", [5432], None, {"fallback": 5432}])
@pytest.mark.parametrize("override", [5433, "remote", [5433], {"primary": 5433}])
def test_set_args_nested_leaf_override(original, override, namespace, reverse):
entries = [
("database", {"host": "localhost", "port": original}),
("database.port", override),
]
if reverse:
entries.reverse()
args: dict[str, Any] | Namespace = dict(entries)
if namespace:
args = Namespace(**args)
before = deepcopy(args)
config = confuse.Configuration("test", read=False)
config.set_args(args, dots=True)
expected = override
if isinstance(original, dict) and isinstance(override, dict):
expected = {**original, **override}
assert config["database"].get() == {"host": "localhost", "port": expected}
assert args == before


@pytest.mark.parametrize("reverse", [False, True])
def test_set_args_none_override_remains_ignored(reverse):
entries = [("database", {"port": 5432}), ("database.port", None)]
if reverse:
entries.reverse()
config = confuse.Configuration("test", read=False)
config.set_args(dict(entries), dots=True)
assert config["database"]["port"].get() == 5432


def test_build_dict_mapping_merge_retains_order_and_is_shallow():
args: dict[str, Any] = {
"database": {"options": {"first": 1, "nested": {"old": 2}}},
"database.options": {"nested": {"new": 3}, "last": 4},
}
result = confuse.util.build_dict(args, sep=".")
assert result == {
"database": {"options": {"first": 1, "nested": {"new": 3}, "last": 4}}
}
assert list(result["database"]["options"]) == ["first", "nested", "last"]
assert args["database"]["options"]["nested"] == {"old": 2}
Loading