diff --git a/pkgs/clan-cli/clan_lib/persist/static_data.py b/pkgs/clan-cli/clan_lib/persist/static_data.py index 1f83b4a06..16286b525 100644 --- a/pkgs/clan-cli/clan_lib/persist/static_data.py +++ b/pkgs/clan-cli/clan_lib/persist/static_data.py @@ -3,10 +3,21 @@ from typing import Any from clan_lib.persist.util import flatten_data, list_difference -def flatten_data_structured(data: dict, parent_path: tuple = ()) -> dict: - """Flatten using tuple keys instead of string concatenation +def flatten_data_structured(data: dict, parent_path: tuple = ()) -> dict[tuple[str, ...], Any]: + """Flatten data using tuple keys instead of string concatenation. + This eliminates ambiguity between literal dots in keys vs nested structure. - Avoids ambiguity issues with keys that contain dots or other separators. + Args: + data: The nested dictionary to flatten + parent_path: Current path as tuple (used for recursion) + + Returns: + Dict with tuple keys representing the full path to each value + + Example: + {"key.foo": "val1", "key": {"foo": "val2"}} + becomes: + {("key.foo",): "val1", ("key", "foo"): "val2"} """ flattened = {} @@ -25,14 +36,14 @@ def flatten_data_structured(data: dict, parent_path: tuple = ()) -> dict: def calculate_static_data( all_values: dict[str, Any], persisted: dict[str, Any] -) -> dict[str, Any]: +) -> dict[tuple[str, ...], Any]: """Calculate the static (read-only) data by finding what exists in all_values but not in persisted data. This gives us a clear view of what cannot be modified/deleted. """ - all_flat = flatten_data(all_values) - persisted_flat = flatten_data(persisted) + all_flat = flatten_data_structured(all_values) + persisted_flat = flatten_data_structured(persisted) static_flat = {} for key, value in all_flat.items(): diff --git a/pkgs/clan-cli/clan_lib/persist/static_data_test.py b/pkgs/clan-cli/clan_lib/persist/static_data_test.py index f2cb21016..ce734e5c7 100644 --- a/pkgs/clan-cli/clan_lib/persist/static_data_test.py +++ b/pkgs/clan-cli/clan_lib/persist/static_data_test.py @@ -86,9 +86,9 @@ def test_calculate_static_data_basic() -> None: } expected_static = { - "settings.optionB": False, - "settings.listSetting": [1, 4], - "staticOnly": "staticValue", + ("settings", "optionB"): False, + ("settings","listSetting"): [1, 4], + ("staticOnly",): "staticValue" } static_data = calculate_static_data(all_values, persisted) @@ -132,11 +132,11 @@ def test_calculate_static_data_all_static() -> None: persisted: dict = {} expected_static = { - "name": "example", - "version": 1, - "settings.optionA": True, - "settings.listSetting": [1, 2, 3], - "staticOnly": "staticValue", + ("name",): "example", + ("version",): 1, + ("settings", "optionA"): True, + ("settings", "listSetting"): [1, 2, 3], + ("staticOnly",): "staticValue", } static_data = calculate_static_data(all_values, persisted) @@ -178,9 +178,9 @@ def test_calculate_nested_dicts() -> None: } expected_static = { - "level1.level2.staticKey": "staticValue", - "level1.anotherStatic": 42, - "topLevelStatic": True, + ("level1", "level2", "staticKey"): "staticValue", + ("level1", "anotherStatic"): 42, + ("topLevelStatic",): True, } static_data = calculate_static_data(all_values, persisted) @@ -188,9 +188,6 @@ def test_calculate_nested_dicts() -> None: def test_dot_in_keys() -> None: - # TODO: This is a bug in the current implementation. - # We need to change the key representation to handle this case correctly. - # For now, we just document the behavior with this test. all_values = { "key.foo": "staticValue", "key": { @@ -200,7 +197,8 @@ def test_dot_in_keys() -> None: persisted: dict = {} expected_static = { - "key.foo": "anotherStaticValue", + ("key.foo",): "staticValue", + ("key", "foo"): "anotherStaticValue", } static_data = calculate_static_data(all_values, persisted)