Skip to content

Commit a7b814a

Browse files
Merge pull request #8 from StructuralPython/features/filter_keys
Features/filter keys
2 parents 8d41cd9 + d4bcb24 commit a7b814a

File tree

4 files changed

+85
-9
lines changed

4 files changed

+85
-9
lines changed

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ authors = [
66
{ name = "Connor Ferster", email = "[email protected]" }
77
]
88
requires-python = ">=3.10"
9-
dependencies = []
9+
dependencies = [
10+
"deepmerge"
11+
]
1012

1113
[project.scripts]
1214
jsonchain = "jsonchain:main"

src/jsonchain/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@
66

77

88
from .io import (load_json, dump_json)
9-
from .envelope import envelope_tree
9+
from .envelope import (
10+
envelope_tree,
11+
max,
12+
min,
13+
absmax,
14+
absmin
15+
)
1016
from .tree import (
1117
compare_tree_values,
1218
extract_keys,
1319
trim_branches,
14-
retrieve_leaves
20+
retrieve_leaves,
21+
filter_keys,
22+
merge_trees
1523
)
1624
from . import tables

src/jsonchain/tree.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def compare_tree_values(
1212
leaf_b: Hashable,
1313
compare_func: Union[str, callable, None],
1414
comparison_key: Optional[Hashable]= None,
15+
comparison_label: Optional[Hashable] = None,
1516
*args,
1617
**kwargs,
1718
) -> dict:
@@ -39,6 +40,10 @@ def compare_tree_values(
3940
'comparison_key' is ignored.
4041
'comparison_key': If provided, will serve as the key for the comparison value.
4142
If None, then the name of the comparison operator will used instead.
43+
'comparison_label': If provided, will add an extra nested layer to the resulting
44+
dictionary keyed with 'comparison_label'. This is useful if you are going
45+
to be merging comparison trees and you wish to uniquely identify each
46+
comparison.
4247
"""
4348
ops = {
4449
"div": operator.truediv,
@@ -59,19 +64,24 @@ def compare_tree_values(
5964

6065
branch_a = trim_branches(subtree_a, levels_a)
6166
branch_b = trim_branches(subtree_b, levels_b)
62-
6367
for trunk in branch_a.keys():
6468
value_a = branch_a[trunk]
69+
if trunk not in branch_b: continue
6570
value_b = branch_b[trunk]
6671
env_acc.setdefault(trunk, {})
67-
env_acc[trunk].setdefault(leaf_a, value_a)
68-
env_acc[trunk].setdefault(leaf_b, value_b)
72+
if comparison_label is not None:
73+
env_acc[trunk].setdefault(comparison_label, {})
74+
compare_acc = env_acc[trunk][comparison_label]
75+
else:
76+
compare_acc = env_acc[trunk]
77+
compare_acc.setdefault(leaf_a, value_a)
78+
compare_acc.setdefault(leaf_b, value_b)
6979
comparison_operator = ops.get(compare_func, compare_func)
7080
if comparison_operator is not None:
7181
compared_value = comparison_operator(value_a, value_b)
7282
if comparison_key is None:
7383
comparison_key = comparison_operator.__name__
74-
env_acc[trunk].setdefault(comparison_key, compared_value)
84+
compare_acc.setdefault(comparison_key, compared_value)
7585
return env_acc
7686

7787

@@ -180,7 +190,50 @@ def extract_keys(
180190

181191
return acc
182192

183-
193+
194+
def filter_keys(
195+
tree: dict,
196+
include_keys: Optional[list[str]] = None,
197+
exclude_keys: Optional[list[str]] = None,
198+
include_keys_startswith: Optional[str | list[str]] = None
199+
) -> dict:
200+
"""
201+
Returns a copy of 'tree' that has had some of its top-level
202+
keys removed based on the applied filters.
203+
204+
Filters:
205+
206+
- include_keys: Provide a list of keys to include
207+
- exclude_keys: Provide a list of keys to exclude
208+
- include_keys_startswith: Provide a substring that
209+
occurs at the start of the keys. All matches will
210+
be included. If a list of substrings are provided,
211+
then all substrings will be checked for a match
212+
and included.
213+
214+
These filters are additive and are applied in the following
215+
order:
216+
217+
include_keys
218+
include_keys_startswith
219+
exclude_keys
220+
"""
221+
include_keys = include_keys or []
222+
exclude_keys = exclude_keys or []
223+
if include_keys_startswith is not None and isinstance(include_keys_startswith, str):
224+
include_keys_startswith = [include_keys_startswith]
225+
filtered_keys = []
226+
for key in tree.keys():
227+
if key in exclude_keys: continue
228+
if include_keys_startswith is not None:
229+
for include_startswith in include_keys_startswith:
230+
if key.startswith(include_startswith):
231+
filtered_keys.append(key)
232+
elif key in include_keys:
233+
filtered_keys.append(key)
234+
filtered_tree = {k: v for k, v in tree.items() if k in filtered_keys}
235+
return filtered_tree
236+
184237

185238
def merge_trees(trees: list[dict[str, dict]]) -> dict[str, dict]:
186239
"""

uv.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)