From 4ec3043a4e4deffc65e29557b62a47937df3b9fa Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 19 Sep 2025 17:30:52 +0200 Subject: [PATCH] clan_lib: init flatten_data_structured Avoids ambiguity issues with keys that contain dots or other separators. --- pkgs/clan-cli/clan_lib/persist/static_data.py | 20 ++++++ .../clan_lib/persist/static_data_test.py | 66 ++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/pkgs/clan-cli/clan_lib/persist/static_data.py b/pkgs/clan-cli/clan_lib/persist/static_data.py index b9681367f..1f83b4a06 100644 --- a/pkgs/clan-cli/clan_lib/persist/static_data.py +++ b/pkgs/clan-cli/clan_lib/persist/static_data.py @@ -3,6 +3,26 @@ 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 + + Avoids ambiguity issues with keys that contain dots or other separators. + + """ + flattened = {} + for key, value in data.items(): + current_path = (*parent_path, key) + + if isinstance(value, dict): + if value: + flattened.update(flatten_data_structured(value, current_path)) + else: + flattened[current_path] = {} + else: + flattened[current_path] = value + return flattened + + def calculate_static_data( all_values: dict[str, Any], persisted: dict[str, Any] ) -> dict[str, Any]: 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 f4f9fca97..f2cb21016 100644 --- a/pkgs/clan-cli/clan_lib/persist/static_data_test.py +++ b/pkgs/clan-cli/clan_lib/persist/static_data_test.py @@ -1,4 +1,68 @@ -from clan_lib.persist.static_data import calculate_static_data +from clan_lib.persist.static_data import calculate_static_data, flatten_data_structured + + +def test_flatten_data_structured() -> None: + data = { + "name": "example", + "settings": { + "optionA": True, + "optionB": { + "subOption1": 10, + "subOption2": 20, + }, + "emptyDict": {}, + }, + "listSetting": [1, 2, 3], + } + + expected_flat = { + ("name",): "example", + ("settings", "optionA"): True, + ("settings", "optionB", "subOption1"): 10, + ("settings", "optionB", "subOption2"): 20, + ("settings", "emptyDict"): {}, + ("listSetting",): [1, 2, 3], + } + + flattened = flatten_data_structured(data) + assert flattened == expected_flat + + +def test_flatten_data_structured_empty() -> None: + data: dict = {} + expected_flat: dict = {} + flattened = flatten_data_structured(data) + assert flattened == expected_flat + + +def test_flatten_data_structured_nested_empty() -> None: + data: dict = { + "level1": { + "level2": { + "level3": {}, + }, + }, + } + expected_flat: dict = { + ("level1", "level2", "level3"): {}, + } + flattened = flatten_data_structured(data) + assert flattened == expected_flat + + +def test_flatten_data_dot_in_keys() -> None: + data = { + "key.foo": "value1", + "key": { + "foo": "value2", + }, + } + expected_flat = { + ("key.foo",): "value1", + ("key", "foo"): "value2", + } + flattened = flatten_data_structured(data) + assert flattened == expected_flat def test_calculate_static_data_basic() -> None: