Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added expand flag to stringify_unsupported() to expand series #1862

Merged
merged 10 commits into from
Oct 31, 2024
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## neptune 1.13.0

### Features
- Added optional `expand` flag to `stringify_unsupported` to expand series ([#1862](https://github.com/neptune-ai/neptune-client/pull/1862))
SiddhantSadangi marked this conversation as resolved.
Show resolved Hide resolved

## neptune 1.12.0

### Changes
Expand Down
11 changes: 9 additions & 2 deletions src/neptune/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@
logger = get_logger()


def stringify_unsupported(value: Any) -> Union[StringifyValue, Mapping]:
def stringify_unsupported(
value: Any,
expand: bool = False,
) -> Union[StringifyValue, Mapping]:
"""Helper function that converts unsupported values in a collection or dictionary to strings.

Args:
value (Any): A dictionary with values or a collection
expand (bool, optional): If True, the function will expand series to store each item as a key-value pair.
SiddhantSadangi marked this conversation as resolved.
Show resolved Hide resolved
Otherwise, the entire series will be logged as a string. Defaults to False.

Example:
>>> import neptune
Expand All @@ -65,7 +70,9 @@ def stringify_unsupported(value: Any) -> Union[StringifyValue, Mapping]:
https://docs.neptune.ai/setup/neptune-client_1-0_release_changes/#no-more-implicit-casting-to-string
"""
if isinstance(value, MutableMapping):
return {str(k): stringify_unsupported(v) for k, v in value.items()}
return {str(k): stringify_unsupported(v, expand=expand) for k, v in value.items()}
if expand and isinstance(value, (list, tuple, set)):
return {str(i): stringify_unsupported(v, expand=True) for i, v in enumerate(value)}

return StringifyValue(value=value)

Expand Down
79 changes: 69 additions & 10 deletions tests/unit/neptune/new/test_stringify_unsupported.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,29 +322,50 @@ def test_assign__tuple_inside_dict(self, run):

def test_assign__dict(self, run):
with assert_unsupported_warning():
run["unsupported"] = {"a": Obj(), "b": "Test", "c": 25, "d": 1997, "e": {"f": Boolean(True)}}
run["unsupported"] = {
"a": Obj(),
"b": "Test",
"c": 25,
"d": 1997,
"e": {"f": Boolean(True)},
}

with assert_no_warnings():
run["stringified"] = stringify_unsupported(
{"a": Obj(), "b": "Test", "c": 25, "d": 1997, "e": {"f": Boolean(True)}}
)

with assert_no_warnings():
run["regular"] = {"a": str(Obj()), "b": "Test", "c": 25, "d": 1997, "e": {"f": Boolean(True)}}
run["regular"] = {
"a": str(Obj()),
"b": "Test",
"c": 25,
"d": 1997,
"e": {"f": Boolean(True)},
}

assert run["regular"].fetch() == run["stringified"].fetch()

def test_assign__mutable_mapping(self, run):
with assert_no_warnings():

run["stringified_mutable_mapping"] = stringify_unsupported(
CustomMutableMapping({5: None, "b": None, "c": (None, Obj())})
)

def test_assign__dict__reassign(self, run):
with assert_unsupported_warning():
run["unsupported"] = {"a": Obj(), "b": "Test", "c": 25, "d": 1997, "e": {"f": Boolean(True)}}
run["unsupported"] = {"a": Obj(name="B"), "d": 12, "e": {"f": Boolean(False)}}
run["unsupported"] = {
"a": Obj(),
"b": "Test",
"c": 25,
"d": 1997,
"e": {"f": Boolean(True)},
}
run["unsupported"] = {
"a": Obj(name="B"),
"d": 12,
"e": {"f": Boolean(False)},
}

with assert_no_warnings():
run["stringified"] = stringify_unsupported(
Expand All @@ -353,8 +374,18 @@ def test_assign__dict__reassign(self, run):
run["stringified"] = stringify_unsupported({"a": Obj(name="B"), "d": 12, "e": {"f": Boolean(False)}})

with assert_no_warnings():
run["regular"] = {"a": str(Obj()), "b": "Test", "c": 25, "d": 1997, "e": {"f": Boolean(True)}}
run["regular"] = {"a": str(Obj(name="B")), "d": 12, "e": {"f": Boolean(False)}}
run["regular"] = {
"a": str(Obj()),
"b": "Test",
"c": 25,
"d": 1997,
"e": {"f": Boolean(True)},
}
run["regular"] = {
"a": str(Obj(name="B")),
"d": 12,
"e": {"f": Boolean(False)},
}

assert run["regular"].fetch() == run["stringified"].fetch()

Expand Down Expand Up @@ -461,16 +492,29 @@ def test_extend__float(self, run):

def test_extend__dict(self, run):
with assert_unsupported_warning():
run["unsupported"].extend({"zz": [1.0, 2.0, 3.0, 4.0, 5.0], "bb": [Obj(), Obj(), Obj(), Obj(), Obj()]})
run["unsupported"].extend(
{
"zz": [1.0, 2.0, 3.0, 4.0, 5.0],
"bb": [Obj(), Obj(), Obj(), Obj(), Obj()],
}
)

with assert_no_warnings():
run["stringified"].extend(
stringify_unsupported({"zz": [1.0, 2.0, 3.0, 4.0, 5.0], "bb": [Obj(), Obj(), Obj(), Obj(), Obj()]})
stringify_unsupported(
{
"zz": [1.0, 2.0, 3.0, 4.0, 5.0],
"bb": [Obj(), Obj(), Obj(), Obj(), Obj()],
}
)
)

with assert_no_warnings():
run["regular"].extend(
{"zz": [1.0, 2.0, 3.0, 4.0, 5.0], "bb": [str(Obj()), str(Obj()), str(Obj()), str(Obj()), str(Obj())]}
{
"zz": [1.0, 2.0, 3.0, 4.0, 5.0],
"bb": [str(Obj()), str(Obj()), str(Obj()), str(Obj()), str(Obj())],
}
)

assert run["regular/zz"].fetch_values().equals(run["stringified/zz"].fetch_values())
Expand Down Expand Up @@ -544,3 +588,18 @@ def test_stringifying_unsupported_floats(self, run):
assert run["neg_infinity"].fetch() == "-inf"

assert math.isnan(float(run["nan"].fetch()))

def test_stringify_unsupported_expand(self, run):
data = {
"a": [1, 2, 3],
"b": {"c": 4, "d": 5},
"e": [{"f": (6, 7)}, {"g": 8}],
}

with assert_no_warnings():
run["data_expanded"] = stringify_unsupported(data, expand=True)

assert run["data_expanded/a/0"].fetch() == 1
assert run["data_expanded/b/c"].fetch() == 4
assert run["data_expanded/e/0/f/0"].fetch() == 6
assert run["data_expanded/e/1/g"].fetch() == 8
Loading