Skip to content

Commit

Permalink
feat(zero_3rdparty): support iterating nested keys from lists
Browse files Browse the repository at this point in the history
  • Loading branch information
EspenAlbert committed Jan 31, 2024
1 parent 2ea30b5 commit c77b2bd
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 4 deletions.
32 changes: 28 additions & 4 deletions zero_3rdparty/src/zero_3rdparty/dict_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ def pop_nested(container: DictList, simple_path: str, default: T = _MISSING) ->
return default


def iter_nested_keys(container: MutableMapping, root_path: str = "") -> Iterable[str]:
def iter_nested_keys(
container: MutableMapping | list,
root_path: str = "",
*,
include_list_indexes: bool = False,
) -> Iterable[str]:
"""
>>> list(iter_nested_keys(dict(a=dict(b="c"))))
['a', 'a.b']
Expand All @@ -52,23 +57,42 @@ def iter_nested_keys(container: MutableMapping, root_path: str = "") -> Iterable
>>> list(iter_nested_keys(dict(a=dict(b="c", c=["1", "2"]), d="2")))
['a', 'a.b', 'a.c', 'd']
"""
if include_list_indexes and isinstance(container, list):
for i, child in enumerate(container):
child_root_path = f"{root_path}.[{i}]"
yield child_root_path
if isinstance(child, (dict, list)):
yield from iter_nested_keys(
child, child_root_path, include_list_indexes=include_list_indexes
)
return
assert isinstance(container, dict), "list only allowed if include_list_indexes=True"
for key, child in container.items():
child_root_path = f"{root_path}.{key}" if root_path else key
if isinstance(key, str):
yield child_root_path
if isinstance(child, dict):
yield from iter_nested_keys(child, child_root_path)
yield from iter_nested_keys(
child, child_root_path, include_list_indexes=include_list_indexes
)
if include_list_indexes and isinstance(child, list):
yield from iter_nested_keys(
child, child_root_path, include_list_indexes=include_list_indexes
)


def iter_nested_key_values(
container: MutableMapping, type_filter: type[T] = _MISSING
container: MutableMapping,
type_filter: type[T] = _MISSING,
*,
include_list_indexes: bool = False,
) -> Iterable[tuple[str, T]]:
"""
>>> container_example = dict(a="ok", b=dict(c="nested"))
>>> list(iter_nested_key_values(container_example, str))
[('a', 'ok'), ('b.c', 'nested')]
"""
for key in iter_nested_keys(container):
for key in iter_nested_keys(container, include_list_indexes=include_list_indexes):
value = read_nested(container, key)
if type_filter is _MISSING or isinstance(value, type_filter):
yield key, value
Expand Down
19 changes: 19 additions & 0 deletions zero_3rdparty/tests/test_zero_3rdparty/test_dict_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from zero_3rdparty.dict_nested import (
iter_nested_key_values,
pop_nested,
read_nested,
read_nested_or_none,
Expand Down Expand Up @@ -86,3 +87,21 @@ def test_pop_nested():
with pytest.raises(KeyError):
pop_nested(active_d, "metadata.name")
assert pop_nested(active_d, "metadata.name", "default") == "default"


def test_iter_nested_in_list_in_child():
d = {"a": [{"b": [{"id": 1}, {"id": "2"}, 3]}, {"c": "d"}]}
strings = list(iter_nested_key_values(d, str, include_list_indexes=True))
assert strings == [
("a.[0].b.[1].id", "2"),
("a.[1].c", "d"),
]
for path, s in strings:
assert read_nested(d, path) == s
ints = list(iter_nested_key_values(d, int, include_list_indexes=True))
assert ints == [
("a.[0].b.[0].id", 1),
("a.[0].b.[2]", 3),
]
for path, i in ints:
assert read_nested(d, path) == i

0 comments on commit c77b2bd

Please sign in to comment.