From 15646980c0b5b989f1751bd2fb445b0c71b14d86 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 14:31:52 +0200 Subject: [PATCH 01/11] feat: added stringify_unsupported() --- src/neptune_scale/utils.py | 55 +++++++++ tests/unit/test_stringify_unsupported.py | 135 +++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 src/neptune_scale/utils.py create mode 100644 tests/unit/test_stringify_unsupported.py diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py new file mode 100644 index 00000000..93187a55 --- /dev/null +++ b/src/neptune_scale/utils.py @@ -0,0 +1,55 @@ +from datetime import datetime +from typing import Any + + +def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: + """ + A helper function that flattens a nested dictionary structure and casts unsupported values to strings to be logged in Neptune. + Note: + - Collections (list, set, tuple) are converted to strings. + - None values are ignored. + + Args: + d: Dictionary to flatten + + Returns: + dict: Flattened dictionary with string keys and cast values + + Example: + >>> config = {"mixed_nested": {"list": [1, {"a": 2}, 3, None], "dict": {"a": [1, 2], "b": {"c": 3}, "d": None}}} + >>> run.log_configs(config) # without `stringify_unsupported()` + neptune:WARNING: Dropping value. Config values must be float, bool, int, str, datetime, list, set or tuple (got `mixed_nested`:`{'list': [1, {'a': 2}, 3, None], 'dict': {'a': [1, 2], 'b': {'c': 3}}}`) + >>> run.log_configs(stringify_unsupported(config)) # with `stringify_unsupported()` + Logged successfully as + "mixed_nested": { + "list": "[1, {'a': 2}, 3, None]", + "dict": { + "a": "[1, 2]", + "b": {"c": 3}, + } + } + + + For more details, see https://docs.neptune.ai/api/utils/#stringify_unsupported + """ + if not isinstance(d, dict): + raise TypeError("Input must be a dictionary") + + allowed_datatypes = [int, float, str, datetime, bool, list, set, tuple] + + flattened = {} + + def _stringify_unsupported(d: dict[str, Any], prefix: str = "") -> None: + for key, value in d.items(): + new_key = f"{prefix}/{key}" if prefix else f"{prefix}{key}" + if isinstance(value, dict): + _stringify_unsupported(d=value, prefix=new_key) + elif isinstance(value, (list, set, tuple)): + flattened[new_key] = str(value) + elif type(value) in allowed_datatypes: + flattened[new_key] = value + elif value is not None: + flattened[new_key] = str(value) + + _stringify_unsupported(d) + return flattened diff --git a/tests/unit/test_stringify_unsupported.py b/tests/unit/test_stringify_unsupported.py new file mode 100644 index 00000000..49e28f2c --- /dev/null +++ b/tests/unit/test_stringify_unsupported.py @@ -0,0 +1,135 @@ +from datetime import datetime +from typing import Any + +import pytest + +from neptune_scale.utils import stringify_unsupported + + +def test_stringify_unsupported_basic_types(): + """Test stringify_unsupported with basic Python types.""" + # Test with basic types + test_cases: dict[str, Any] = { + "string": "hello", + "integer": 42, + "float": 3.14, + "boolean": True, + "none_value": None, + "datetime": datetime.now(), + } + + result = stringify_unsupported(test_cases) + + assert result["string"] == "hello" + assert result["integer"] == 42 + assert result["float"] == 3.14 + assert result["boolean"] is True + assert "none_value" not in result + assert isinstance(result["datetime"], datetime) + + +def test_stringify_unsupported_collections(): + """Test stringify_unsupported with collection types.""" + test_cases = {"list": ["a", "b", "c"], "tuple": (1, 2, 3), "set": {1, 2, 3}} + + result = stringify_unsupported(test_cases) + + assert result["list"] == "['a', 'b', 'c']" + assert result["tuple"] == "(1, 2, 3)" + assert result["set"] == "{1, 2, 3}" + + +def test_stringify_unsupported_nested(): + """Test stringify_unsupported with nested dictionaries.""" + test_cases = {"top": {"middle": {"bottom": "value"}}} + + result = stringify_unsupported(test_cases) + + assert result["top/middle/bottom"] == "value" + + +def test_stringify_unsupported_mixed_types(): + """Test stringify_unsupported with mixed types in collections.""" + test_cases = { + "mixed_list": [1, "two", 3.0, True, None], + "mixed_dict": {"str": "value", "int": 123, "list": [1, 2, 3], "none": None}, + } + + result = stringify_unsupported(test_cases) + + assert result["mixed_list"] == "[1, 'two', 3.0, True, None]" + assert result["mixed_dict/str"] == "value" + assert result["mixed_dict/int"] == 123 + assert result["mixed_dict/list"] == "[1, 2, 3]" + assert "mixed_dict/none" not in result + + +def test_stringify_unsupported_edge_cases(): + """Test stringify_unsupported with edge cases.""" + test_cases = {"empty_list": [], "empty_dict": {}, "empty_string": "", "zero": 0, "false": False} + + result = stringify_unsupported(test_cases) + + assert result["empty_list"] == "[]" + assert "empty_dict" not in result + assert result["empty_string"] == "" + assert result["zero"] == 0 + assert result["false"] is False + + +def test_stringify_unsupported_complex_nested(): + """Test stringify_unsupported with complex nested structures.""" + test_cases = { + "complex": { + "list_of_dicts": [{"id": 1, "name": "one"}, {"id": 2, "name": "two"}], + "mixed_nested": {"list": [1, {"a": 2}, 3, None], "dict": {"a": [1, 2], "b": {"c": 3}}}, + } + } + + result = stringify_unsupported(test_cases) + + assert result["complex/list_of_dicts"] == "[{'id': 1, 'name': 'one'}, {'id': 2, 'name': 'two'}]" + assert result["complex/mixed_nested/list"] == "[1, {'a': 2}, 3, None]" + assert result["complex/mixed_nested/dict/a"] == "[1, 2]" + assert result["complex/mixed_nested/dict/b/c"] == 3 + + +def test_stringify_unsupported_custom_objects(): + """Test stringify_unsupported with custom objects.""" + + class CustomObject: + def __str__(self): + return "custom_object" + + test_cases = {"custom": CustomObject()} + + result = stringify_unsupported(test_cases) + + assert result["custom"] == "custom_object" + + +def test_stringify_unsupported_none_values(): + """Test stringify_unsupported with None values in collections.""" + test_cases = {"list_with_none": [1, None, 3], "dict_with_none": {"a": 1, "b": None, "c": 3}} + + result = stringify_unsupported(test_cases) + + assert result["list_with_none"] == "[1, None, 3]" + assert result["dict_with_none/a"] == 1 + assert "dict_with_none/b" not in result + assert result["dict_with_none/c"] == 3 + + +def test_stringify_unsupported_empty_input(): + """Test stringify_unsupported with empty input.""" + result = stringify_unsupported({}) + assert result == {} + + +def test_stringify_unsupported_invalid_input(): + """Test stringify_unsupported with invalid input.""" + with pytest.raises(TypeError): + stringify_unsupported(None) # type: ignore + + with pytest.raises(TypeError): + stringify_unsupported("not a dict") # type: ignore From 91b29b601c8879e21f93c4d6c2e4716141638928 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 14:50:48 +0200 Subject: [PATCH 02/11] tests: updated docstrings for doctests --- src/neptune_scale/utils.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index 93187a55..1ee10632 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -2,7 +2,7 @@ from typing import Any -def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: +def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: # noqa: C901 """ A helper function that flattens a nested dictionary structure and casts unsupported values to strings to be logged in Neptune. Note: @@ -11,23 +11,22 @@ def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: Args: d: Dictionary to flatten + **kwargs: Additional arguments for backward compatibility Returns: dict: Flattened dictionary with string keys and cast values Example: + >>> from neptune_scale import Run + >>> run = Run() >>> config = {"mixed_nested": {"list": [1, {"a": 2}, 3, None], "dict": {"a": [1, 2], "b": {"c": 3}, "d": None}}} - >>> run.log_configs(config) # without `stringify_unsupported()` + >>> # without `stringify_unsupported()` + >>> run.log_configs(config) # doctest: +SKIP neptune:WARNING: Dropping value. Config values must be float, bool, int, str, datetime, list, set or tuple (got `mixed_nested`:`{'list': [1, {'a': 2}, 3, None], 'dict': {'a': [1, 2], 'b': {'c': 3}}}`) - >>> run.log_configs(stringify_unsupported(config)) # with `stringify_unsupported()` - Logged successfully as - "mixed_nested": { - "list": "[1, {'a': 2}, 3, None]", - "dict": { - "a": "[1, 2]", - "b": {"c": 3}, - } - } + >>> # with `stringify_unsupported()` + >>> from neptune_scale.utils import stringify_unsupported + >>> run.log_configs(stringify_unsupported(config)) # Logged successfully + For more details, see https://docs.neptune.ai/api/utils/#stringify_unsupported From f1a61c0180699b1306dc9a03bf173760e25d98ae Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 14:55:19 +0200 Subject: [PATCH 03/11] tests: removed example from docstrings --- src/neptune_scale/utils.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index 1ee10632..ef9a3cab 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -16,19 +16,6 @@ def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: Returns: dict: Flattened dictionary with string keys and cast values - Example: - >>> from neptune_scale import Run - >>> run = Run() - >>> config = {"mixed_nested": {"list": [1, {"a": 2}, 3, None], "dict": {"a": [1, 2], "b": {"c": 3}, "d": None}}} - >>> # without `stringify_unsupported()` - >>> run.log_configs(config) # doctest: +SKIP - neptune:WARNING: Dropping value. Config values must be float, bool, int, str, datetime, list, set or tuple (got `mixed_nested`:`{'list': [1, {'a': 2}, 3, None], 'dict': {'a': [1, 2], 'b': {'c': 3}}}`) - >>> # with `stringify_unsupported()` - >>> from neptune_scale.utils import stringify_unsupported - >>> run.log_configs(stringify_unsupported(config)) # Logged successfully - - - For more details, see https://docs.neptune.ai/api/utils/#stringify_unsupported """ if not isinstance(d, dict): From b0c17429f9cf185a955eb856629f5c72f9634b6b Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 14:59:05 +0200 Subject: [PATCH 04/11] chore: applied code review suggestions --- src/neptune_scale/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index ef9a3cab..e5ed7075 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -27,7 +27,7 @@ def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: def _stringify_unsupported(d: dict[str, Any], prefix: str = "") -> None: for key, value in d.items(): - new_key = f"{prefix}/{key}" if prefix else f"{prefix}{key}" + new_key = f"{prefix}/{key}" if prefix else key if isinstance(value, dict): _stringify_unsupported(d=value, prefix=new_key) elif isinstance(value, (list, set, tuple)): From 1170483c2c1ea7df3a92e52758855187bc5f7bed Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 15:02:57 +0200 Subject: [PATCH 05/11] chore: removed unecessary `noqa` directive --- src/neptune_scale/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index e5ed7075..35942e83 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -2,7 +2,7 @@ from typing import Any -def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: # noqa: C901 +def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: """ A helper function that flattens a nested dictionary structure and casts unsupported values to strings to be logged in Neptune. Note: From eff02c6c104b1c232f18c2acc16238d7d7c20423 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 16:22:32 +0200 Subject: [PATCH 06/11] Update src/neptune_scale/utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/neptune_scale/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index 35942e83..742bdd66 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -32,7 +32,7 @@ def _stringify_unsupported(d: dict[str, Any], prefix: str = "") -> None: _stringify_unsupported(d=value, prefix=new_key) elif isinstance(value, (list, set, tuple)): flattened[new_key] = str(value) - elif type(value) in allowed_datatypes: + elif isinstance(value, tuple(allowed_datatypes)): flattened[new_key] = value elif value is not None: flattened[new_key] = str(value) From 503b8232a61672b88a6353309d59872664bacbb1 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 16:32:57 +0200 Subject: [PATCH 07/11] Revert "Update src/neptune_scale/utils.py" This reverts commit eff02c6c104b1c232f18c2acc16238d7d7c20423. --- src/neptune_scale/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index 742bdd66..35942e83 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -32,7 +32,7 @@ def _stringify_unsupported(d: dict[str, Any], prefix: str = "") -> None: _stringify_unsupported(d=value, prefix=new_key) elif isinstance(value, (list, set, tuple)): flattened[new_key] = str(value) - elif isinstance(value, tuple(allowed_datatypes)): + elif type(value) in allowed_datatypes: flattened[new_key] = value elif value is not None: flattened[new_key] = str(value) From a36cdace0810c532cb6ff476638ebff590d04ef3 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 16:56:27 +0200 Subject: [PATCH 08/11] feat: expanded to all collections --- src/neptune_scale/utils.py | 7 ++-- tests/unit/test_stringify_unsupported.py | 42 +++++++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index 35942e83..ce316044 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -1,3 +1,4 @@ +from collections.abc import Collection from datetime import datetime from typing import Any @@ -6,7 +7,7 @@ def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: """ A helper function that flattens a nested dictionary structure and casts unsupported values to strings to be logged in Neptune. Note: - - Collections (list, set, tuple) are converted to strings. + - Sequence and Set collections (like list, tuple, set, etc.) are converted to strings. - None values are ignored. Args: @@ -21,7 +22,7 @@ def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: if not isinstance(d, dict): raise TypeError("Input must be a dictionary") - allowed_datatypes = [int, float, str, datetime, bool, list, set, tuple] + allowed_datatypes = [int, float, str, datetime, bool] flattened = {} @@ -30,7 +31,7 @@ def _stringify_unsupported(d: dict[str, Any], prefix: str = "") -> None: new_key = f"{prefix}/{key}" if prefix else key if isinstance(value, dict): _stringify_unsupported(d=value, prefix=new_key) - elif isinstance(value, (list, set, tuple)): + elif isinstance(value, Collection): flattened[new_key] = str(value) elif type(value) in allowed_datatypes: flattened[new_key] = value diff --git a/tests/unit/test_stringify_unsupported.py b/tests/unit/test_stringify_unsupported.py index 49e28f2c..08fed4ff 100644 --- a/tests/unit/test_stringify_unsupported.py +++ b/tests/unit/test_stringify_unsupported.py @@ -30,13 +30,19 @@ def test_stringify_unsupported_basic_types(): def test_stringify_unsupported_collections(): """Test stringify_unsupported with collection types.""" - test_cases = {"list": ["a", "b", "c"], "tuple": (1, 2, 3), "set": {1, 2, 3}} + test_cases = { + "list": ["a", "b", "c"], + "tuple": (1, 2, 3), + "set": {1, 2, 3}, + "frozenset": frozenset({1, 2, 3}), + } result = stringify_unsupported(test_cases) assert result["list"] == "['a', 'b', 'c']" assert result["tuple"] == "(1, 2, 3)" assert result["set"] == "{1, 2, 3}" + assert result["frozenset"] == "frozenset({1, 2, 3})" def test_stringify_unsupported_nested(): @@ -53,6 +59,8 @@ def test_stringify_unsupported_mixed_types(): test_cases = { "mixed_list": [1, "two", 3.0, True, None], "mixed_dict": {"str": "value", "int": 123, "list": [1, 2, 3], "none": None}, + "mixed_set": {1, "two", 3.0, True}, + "mixed_tuple": (1, "two", 3.0, True, None), } result = stringify_unsupported(test_cases) @@ -62,11 +70,21 @@ def test_stringify_unsupported_mixed_types(): assert result["mixed_dict/int"] == 123 assert result["mixed_dict/list"] == "[1, 2, 3]" assert "mixed_dict/none" not in result + assert result["mixed_set"] == str(test_cases["mixed_set"]) + assert result["mixed_tuple"] == str(test_cases["mixed_tuple"]) def test_stringify_unsupported_edge_cases(): """Test stringify_unsupported with edge cases.""" - test_cases = {"empty_list": [], "empty_dict": {}, "empty_string": "", "zero": 0, "false": False} + test_cases = { + "empty_list": [], + "empty_dict": {}, + "empty_string": "", + "zero": 0, + "false": False, + "empty_set": set(), + "empty_tuple": (), + } result = stringify_unsupported(test_cases) @@ -75,6 +93,8 @@ def test_stringify_unsupported_edge_cases(): assert result["empty_string"] == "" assert result["zero"] == 0 assert result["false"] is False + assert result["empty_set"] == "set()" + assert result["empty_tuple"] == "()" def test_stringify_unsupported_complex_nested(): @@ -82,7 +102,12 @@ def test_stringify_unsupported_complex_nested(): test_cases = { "complex": { "list_of_dicts": [{"id": 1, "name": "one"}, {"id": 2, "name": "two"}], - "mixed_nested": {"list": [1, {"a": 2}, 3, None], "dict": {"a": [1, 2], "b": {"c": 3}}}, + "mixed_nested": { + "list": [1, {"a": 2}, 3, None], + "dict": {"a": [1, 2], "b": {"c": 3}}, + "set": {1, 2, 3}, + "tuple": (1, 2, 3), + }, } } @@ -92,6 +117,8 @@ def test_stringify_unsupported_complex_nested(): assert result["complex/mixed_nested/list"] == "[1, {'a': 2}, 3, None]" assert result["complex/mixed_nested/dict/a"] == "[1, 2]" assert result["complex/mixed_nested/dict/b/c"] == 3 + assert result["complex/mixed_nested/set"] == "{1, 2, 3}" + assert result["complex/mixed_nested/tuple"] == "(1, 2, 3)" def test_stringify_unsupported_custom_objects(): @@ -110,7 +137,12 @@ def __str__(self): def test_stringify_unsupported_none_values(): """Test stringify_unsupported with None values in collections.""" - test_cases = {"list_with_none": [1, None, 3], "dict_with_none": {"a": 1, "b": None, "c": 3}} + test_cases = { + "list_with_none": [1, None, 3], + "dict_with_none": {"a": 1, "b": None, "c": 3}, + "set_with_none": {1, None, 3}, + "tuple_with_none": (1, None, 3), + } result = stringify_unsupported(test_cases) @@ -118,6 +150,8 @@ def test_stringify_unsupported_none_values(): assert result["dict_with_none/a"] == 1 assert "dict_with_none/b" not in result assert result["dict_with_none/c"] == 3 + assert result["set_with_none"] == "{None, 1, 3}" + assert result["tuple_with_none"] == "(1, None, 3)" def test_stringify_unsupported_empty_input(): From add2fcbefd61d8fa0b2b796d0c3389627b03f390 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 14 Apr 2025 17:35:07 +0200 Subject: [PATCH 09/11] tests: updated tests --- tests/unit/test_stringify_unsupported.py | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/unit/test_stringify_unsupported.py b/tests/unit/test_stringify_unsupported.py index 08fed4ff..554c7f8e 100644 --- a/tests/unit/test_stringify_unsupported.py +++ b/tests/unit/test_stringify_unsupported.py @@ -20,9 +20,9 @@ def test_stringify_unsupported_basic_types(): result = stringify_unsupported(test_cases) - assert result["string"] == "hello" - assert result["integer"] == 42 - assert result["float"] == 3.14 + assert result["string"] == test_cases["string"] + assert result["integer"] == test_cases["integer"] + assert result["float"] == test_cases["float"] assert result["boolean"] is True assert "none_value" not in result assert isinstance(result["datetime"], datetime) @@ -39,10 +39,10 @@ def test_stringify_unsupported_collections(): result = stringify_unsupported(test_cases) - assert result["list"] == "['a', 'b', 'c']" - assert result["tuple"] == "(1, 2, 3)" - assert result["set"] == "{1, 2, 3}" - assert result["frozenset"] == "frozenset({1, 2, 3})" + assert result["list"] == str(test_cases["list"]) + assert result["tuple"] == str(test_cases["tuple"]) + assert result["set"] == str(test_cases["set"]) + assert result["frozenset"] == str(test_cases["frozenset"]) def test_stringify_unsupported_nested(): @@ -51,7 +51,7 @@ def test_stringify_unsupported_nested(): result = stringify_unsupported(test_cases) - assert result["top/middle/bottom"] == "value" + assert result["top/middle/bottom"] == test_cases["top"]["middle"]["bottom"] def test_stringify_unsupported_mixed_types(): @@ -65,10 +65,10 @@ def test_stringify_unsupported_mixed_types(): result = stringify_unsupported(test_cases) - assert result["mixed_list"] == "[1, 'two', 3.0, True, None]" - assert result["mixed_dict/str"] == "value" - assert result["mixed_dict/int"] == 123 - assert result["mixed_dict/list"] == "[1, 2, 3]" + assert result["mixed_list"] == str(test_cases["mixed_list"]) + assert result["mixed_dict/str"] == test_cases["mixed_dict"]["str"] + assert result["mixed_dict/int"] == test_cases["mixed_dict"]["int"] + assert result["mixed_dict/list"] == str(test_cases["mixed_dict"]["list"]) assert "mixed_dict/none" not in result assert result["mixed_set"] == str(test_cases["mixed_set"]) assert result["mixed_tuple"] == str(test_cases["mixed_tuple"]) @@ -88,13 +88,13 @@ def test_stringify_unsupported_edge_cases(): result = stringify_unsupported(test_cases) - assert result["empty_list"] == "[]" + assert result["empty_list"] == str(test_cases["empty_list"]) assert "empty_dict" not in result - assert result["empty_string"] == "" + assert result["empty_string"] == str(test_cases["empty_string"]) assert result["zero"] == 0 assert result["false"] is False - assert result["empty_set"] == "set()" - assert result["empty_tuple"] == "()" + assert result["empty_set"] == str(test_cases["empty_set"]) + assert result["empty_tuple"] == str(test_cases["empty_tuple"]) def test_stringify_unsupported_complex_nested(): @@ -113,12 +113,12 @@ def test_stringify_unsupported_complex_nested(): result = stringify_unsupported(test_cases) - assert result["complex/list_of_dicts"] == "[{'id': 1, 'name': 'one'}, {'id': 2, 'name': 'two'}]" - assert result["complex/mixed_nested/list"] == "[1, {'a': 2}, 3, None]" - assert result["complex/mixed_nested/dict/a"] == "[1, 2]" - assert result["complex/mixed_nested/dict/b/c"] == 3 - assert result["complex/mixed_nested/set"] == "{1, 2, 3}" - assert result["complex/mixed_nested/tuple"] == "(1, 2, 3)" + assert result["complex/list_of_dicts"] == str(test_cases["complex"]["list_of_dicts"]) + assert result["complex/mixed_nested/list"] == str(test_cases["complex"]["mixed_nested"]["list"]) + assert result["complex/mixed_nested/dict/a"] == str(test_cases["complex"]["mixed_nested"]["dict"]["a"]) + assert result["complex/mixed_nested/dict/b/c"] == test_cases["complex"]["mixed_nested"]["dict"]["b"]["c"] + assert result["complex/mixed_nested/set"] == str(test_cases["complex"]["mixed_nested"]["set"]) + assert result["complex/mixed_nested/tuple"] == str(test_cases["complex"]["mixed_nested"]["tuple"]) def test_stringify_unsupported_custom_objects(): @@ -146,12 +146,12 @@ def test_stringify_unsupported_none_values(): result = stringify_unsupported(test_cases) - assert result["list_with_none"] == "[1, None, 3]" - assert result["dict_with_none/a"] == 1 + assert result["list_with_none"] == str(test_cases["list_with_none"]) + assert result["dict_with_none/a"] == test_cases["dict_with_none"]["a"] assert "dict_with_none/b" not in result - assert result["dict_with_none/c"] == 3 - assert result["set_with_none"] == "{None, 1, 3}" - assert result["tuple_with_none"] == "(1, None, 3)" + assert result["dict_with_none/c"] == test_cases["dict_with_none"]["c"] + assert result["set_with_none"] == str(test_cases["set_with_none"]) + assert result["tuple_with_none"] == str(test_cases["tuple_with_none"]) def test_stringify_unsupported_empty_input(): From fdfb2ce475a0690ed541068156af66073403df6e Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Mon, 12 May 2025 16:28:47 +0200 Subject: [PATCH 10/11] Code review suggestions --- src/neptune_scale/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index ce316044..527d8961 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -1,9 +1,8 @@ -from collections.abc import Collection from datetime import datetime from typing import Any -def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: +def stringify_unsupported(d: dict[str, Any]) -> dict[str, Any]: """ A helper function that flattens a nested dictionary structure and casts unsupported values to strings to be logged in Neptune. Note: @@ -12,7 +11,6 @@ def stringify_unsupported(d: dict[str, Any], **kwargs: Any) -> dict[str, Any]: Args: d: Dictionary to flatten - **kwargs: Additional arguments for backward compatibility Returns: dict: Flattened dictionary with string keys and cast values @@ -31,8 +29,6 @@ def _stringify_unsupported(d: dict[str, Any], prefix: str = "") -> None: new_key = f"{prefix}/{key}" if prefix else key if isinstance(value, dict): _stringify_unsupported(d=value, prefix=new_key) - elif isinstance(value, Collection): - flattened[new_key] = str(value) elif type(value) in allowed_datatypes: flattened[new_key] = value elif value is not None: From 07ed540b398c16b762abf259e45fc4977f4dc3c5 Mon Sep 17 00:00:00 2001 From: Siddhant Sadangi Date: Wed, 14 May 2025 13:10:27 +0200 Subject: [PATCH 11/11] Update src/neptune_scale/utils.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sabine Ståhlberg --- src/neptune_scale/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/neptune_scale/utils.py b/src/neptune_scale/utils.py index 527d8961..75cc9f86 100644 --- a/src/neptune_scale/utils.py +++ b/src/neptune_scale/utils.py @@ -15,7 +15,7 @@ def stringify_unsupported(d: dict[str, Any]) -> dict[str, Any]: Returns: dict: Flattened dictionary with string keys and cast values - For more details, see https://docs.neptune.ai/api/utils/#stringify_unsupported + For details, see https://docs.neptune.ai/utils/stringify_unsupported """ if not isinstance(d, dict): raise TypeError("Input must be a dictionary")