halib 0.2.20__tar.gz → 0.2.23__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {halib-0.2.20 → halib-0.2.23}/PKG-INFO +6 -2
- {halib-0.2.20 → halib-0.2.23}/README.md +5 -1
- {halib-0.2.20 → halib-0.2.23}/halib/__init__.py +11 -5
- {halib-0.2.20 → halib-0.2.23}/halib/common/common.py +29 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/core/param_gen.py +19 -38
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/perfcalc.py +31 -1
- halib-0.2.23/halib/utils/dict.py +156 -0
- {halib-0.2.20 → halib-0.2.23}/halib.egg-info/PKG-INFO +6 -2
- {halib-0.2.20 → halib-0.2.23}/setup.py +1 -1
- halib-0.2.20/halib/utils/dict.py +0 -9
- {halib-0.2.20 → halib-0.2.23}/.gitignore +0 -0
- {halib-0.2.20 → halib-0.2.23}/GDriveFolder.txt +0 -0
- {halib-0.2.20 → halib-0.2.23}/LICENSE.txt +0 -0
- {halib-0.2.20 → halib-0.2.23}/MANIFEST.in +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/common/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/common/rich_color.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/core/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/core/base_config.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/core/base_exp.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/core/wandb_op.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/data/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/data/dataclass_util.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/data/dataset.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/data/torchloader.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/flop_calc.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/gpu_mon.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/perfmetrics.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/perftb.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/perf/profiler.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/viz/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/exp/viz/plot.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/csvfile.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/ipynb.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/jsonfile.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/textfile.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/videofile.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/filetype/yamlfile.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/online/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/online/gdrive.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/online/gdrive_mkdir.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/online/projectmake.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/online/tele_noti.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/system/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/system/_list_pc.csv +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/system/cmd.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/system/filesys.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/system/path.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/utils/__init__.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib/utils/list.py +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib.egg-info/SOURCES.txt +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib.egg-info/dependency_links.txt +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib.egg-info/requires.txt +0 -0
- {halib-0.2.20 → halib-0.2.23}/halib.egg-info/top_level.txt +0 -0
- {halib-0.2.20 → halib-0.2.23}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: halib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
4
4
|
Summary: Small library for common tasks
|
|
5
5
|
Author: Hoang Van Ha
|
|
6
6
|
Author-email: hoangvanhauit@gmail.com
|
|
@@ -56,7 +56,11 @@ Dynamic: summary
|
|
|
56
56
|
|
|
57
57
|
## v0.2.x (Experiment & Core Updates)
|
|
58
58
|
|
|
59
|
-
### **v0.2.
|
|
59
|
+
### **v0.2.23**
|
|
60
|
+
- ✨ **New Feature:** Added `utils.dict.DictUtils` for advanced dictionary manipulations (merging, filtering, transforming).
|
|
61
|
+
|
|
62
|
+
- ✨ **New Feature:** Added `common.common.pprint_stack_trace` to print stack traces with optional custom messages and force stop capability.
|
|
63
|
+
|
|
60
64
|
- 🚀 **Improvement:** `exp.perf.profiler` - allow to export *report dict* as csv files for further analysis
|
|
61
65
|
|
|
62
66
|
### **v0.2.19**
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## v0.2.x (Experiment & Core Updates)
|
|
4
4
|
|
|
5
|
-
### **v0.2.
|
|
5
|
+
### **v0.2.23**
|
|
6
|
+
- ✨ **New Feature:** Added `utils.dict.DictUtils` for advanced dictionary manipulations (merging, filtering, transforming).
|
|
7
|
+
|
|
8
|
+
- ✨ **New Feature:** Added `common.common.pprint_stack_trace` to print stack traces with optional custom messages and force stop capability.
|
|
9
|
+
|
|
6
10
|
- 🚀 **Improvement:** `exp.perf.profiler` - allow to export *report dict* as csv files for further analysis
|
|
7
11
|
|
|
8
12
|
### **v0.2.19**
|
|
@@ -19,11 +19,11 @@ __all__ = [
|
|
|
19
19
|
"os",
|
|
20
20
|
"pd",
|
|
21
21
|
"plt",
|
|
22
|
-
"pprint",
|
|
23
22
|
"pprint_box",
|
|
24
23
|
"pprint_local_path",
|
|
24
|
+
"pprint_stack_trace",
|
|
25
|
+
"pprint",
|
|
25
26
|
"px",
|
|
26
|
-
"pprint_local_path",
|
|
27
27
|
"rcolor_all_str",
|
|
28
28
|
"rcolor_palette_all",
|
|
29
29
|
"rcolor_palette",
|
|
@@ -32,10 +32,10 @@ __all__ = [
|
|
|
32
32
|
"rprint",
|
|
33
33
|
"sns",
|
|
34
34
|
"tcuda",
|
|
35
|
+
"time",
|
|
35
36
|
"timebudget",
|
|
36
37
|
"tqdm",
|
|
37
38
|
"warnings",
|
|
38
|
-
"time",
|
|
39
39
|
]
|
|
40
40
|
import warnings
|
|
41
41
|
|
|
@@ -64,7 +64,8 @@ from .common.common import (
|
|
|
64
64
|
norm_str,
|
|
65
65
|
pprint_box,
|
|
66
66
|
pprint_local_path,
|
|
67
|
-
|
|
67
|
+
pprint_stack_trace,
|
|
68
|
+
tcuda,
|
|
68
69
|
)
|
|
69
70
|
|
|
70
71
|
# for log
|
|
@@ -76,7 +77,12 @@ from timebudget import timebudget
|
|
|
76
77
|
import omegaconf
|
|
77
78
|
from omegaconf import OmegaConf
|
|
78
79
|
from omegaconf.dictconfig import DictConfig
|
|
79
|
-
from .common.rich_color import
|
|
80
|
+
from .common.rich_color import (
|
|
81
|
+
rcolor_str,
|
|
82
|
+
rcolor_palette,
|
|
83
|
+
rcolor_palette_all,
|
|
84
|
+
rcolor_all_str,
|
|
85
|
+
)
|
|
80
86
|
|
|
81
87
|
# for visualization
|
|
82
88
|
import seaborn as sns
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import sys
|
|
2
3
|
import re
|
|
3
4
|
import arrow
|
|
4
5
|
import importlib
|
|
@@ -10,6 +11,8 @@ from rich.console import Console
|
|
|
10
11
|
from rich.pretty import pprint, Pretty
|
|
11
12
|
|
|
12
13
|
from pathlib import Path, PureWindowsPath
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from loguru import logger
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
console = Console()
|
|
@@ -114,6 +117,32 @@ def linux_to_wins_path(path: str) -> str:
|
|
|
114
117
|
return str(PureWindowsPath(win_path))
|
|
115
118
|
|
|
116
119
|
|
|
120
|
+
DEFAULT_STACK_TRACE_MSG = "Caused by halib.common.common.pprint_stack_trace"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def pprint_stack_trace(
|
|
124
|
+
msg: str = DEFAULT_STACK_TRACE_MSG,
|
|
125
|
+
e: Optional[Exception] = None,
|
|
126
|
+
force_stop: bool = False,
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Print the current stack trace or the stack trace of an exception.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
if e is not None:
|
|
133
|
+
raise e
|
|
134
|
+
else:
|
|
135
|
+
raise Exception("pprint_stack_trace called")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
# attach the exception trace to a warning
|
|
138
|
+
global DEFAULT_STACK_TRACE_MSG
|
|
139
|
+
if len(msg.strip()) == 0:
|
|
140
|
+
msg = DEFAULT_STACK_TRACE_MSG
|
|
141
|
+
logger.opt(exception=e).warning(msg)
|
|
142
|
+
if force_stop:
|
|
143
|
+
console.rule("[red]Force Stop Triggered in <halib.common.pprint_stack_trace>[/red]")
|
|
144
|
+
sys.exit(1)
|
|
145
|
+
|
|
117
146
|
def pprint_local_path(
|
|
118
147
|
local_path: str, get_wins_path: bool = False, tag: str = ""
|
|
119
148
|
) -> str:
|
|
@@ -4,6 +4,11 @@ import numpy as np
|
|
|
4
4
|
from itertools import product
|
|
5
5
|
from typing import Dict, Any, List, Iterator, Optional
|
|
6
6
|
from ...filetype import yamlfile
|
|
7
|
+
from ...utils.dict import DictUtils
|
|
8
|
+
|
|
9
|
+
# Assuming DictUtils is available in the scope or imported
|
|
10
|
+
# from .dict_utils import DictUtils
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
class ParamGen:
|
|
9
14
|
"""
|
|
@@ -38,6 +43,7 @@ class ParamGen:
|
|
|
38
43
|
keys (List[str]): List of flattened dot-notation keys being swept.
|
|
39
44
|
values (List[List[Any]]): List of value options for each key.
|
|
40
45
|
"""
|
|
46
|
+
|
|
41
47
|
def __init__(
|
|
42
48
|
self, sweep_cfg: Dict[str, Any], base_cfg: Optional[Dict[str, Any]] = None
|
|
43
49
|
):
|
|
@@ -50,7 +56,12 @@ class ParamGen:
|
|
|
50
56
|
self.base_cfg = base_cfg if base_cfg is not None else {}
|
|
51
57
|
|
|
52
58
|
# Recursively flatten the nested sweep config into dot-notation keys
|
|
53
|
-
|
|
59
|
+
# Refactored to use DictUtils, passing our custom leaf logic
|
|
60
|
+
flat_sweep = DictUtils.flatten(sweep_cfg, is_leaf_predicate=self._is_sweep_leaf)
|
|
61
|
+
|
|
62
|
+
# Expand values (ranges, strings) which DictUtils leaves as-is
|
|
63
|
+
self.param_space = {k: self._expand_val(v) for k, v in flat_sweep.items()}
|
|
64
|
+
|
|
54
65
|
self.keys = list(self.param_space.keys())
|
|
55
66
|
self.values = list(self.param_space.values())
|
|
56
67
|
|
|
@@ -66,13 +77,16 @@ class ParamGen:
|
|
|
66
77
|
|
|
67
78
|
# 2. Deep copy base and update with current params
|
|
68
79
|
new_cfg = copy.deepcopy(self.base_cfg)
|
|
69
|
-
|
|
80
|
+
|
|
81
|
+
# Refactored: Unflatten the specific params, then deep merge
|
|
82
|
+
update_structure = DictUtils.unflatten(flat_params)
|
|
83
|
+
DictUtils.deep_update(new_cfg, update_structure)
|
|
70
84
|
|
|
71
85
|
# 3. Store metadata (Optional)
|
|
72
86
|
# if "_meta" not in new_cfg:
|
|
73
87
|
# new_cfg["_meta"] = {}
|
|
74
88
|
# We unflatten the sweep params here so the log is readable
|
|
75
|
-
# new_cfg["_meta"]["sweep_params"] =
|
|
89
|
+
# new_cfg["_meta"]["sweep_params"] = DictUtils.unflatten(flat_params)
|
|
76
90
|
|
|
77
91
|
yield new_cfg
|
|
78
92
|
|
|
@@ -124,27 +138,8 @@ class ParamGen:
|
|
|
124
138
|
combinations.append(flat_dict)
|
|
125
139
|
return combinations
|
|
126
140
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
nested = {}
|
|
130
|
-
self._apply_updates(nested, flat_dict)
|
|
131
|
-
return nested
|
|
132
|
-
|
|
133
|
-
def _flatten_params(
|
|
134
|
-
self, cfg: Dict[str, Any], parent_key: str = ""
|
|
135
|
-
) -> Dict[str, List[Any]]:
|
|
136
|
-
"""Recursively converts nested dicts into flat dot-notation keys."""
|
|
137
|
-
flat = {}
|
|
138
|
-
for key, val in cfg.items():
|
|
139
|
-
current_key = f"{parent_key}.{key}" if parent_key else key
|
|
140
|
-
|
|
141
|
-
if self._is_sweep_leaf(val):
|
|
142
|
-
flat[current_key] = self._expand_val(val)
|
|
143
|
-
elif isinstance(val, dict):
|
|
144
|
-
flat.update(self._flatten_params(val, current_key))
|
|
145
|
-
else:
|
|
146
|
-
flat[current_key] = [val]
|
|
147
|
-
return flat
|
|
141
|
+
# Note: _unflatten, _flatten_params, and _apply_updates have been removed
|
|
142
|
+
# as they are replaced by DictUtils methods.
|
|
148
143
|
|
|
149
144
|
def _is_sweep_leaf(self, val: Any) -> bool:
|
|
150
145
|
if isinstance(val, list):
|
|
@@ -173,17 +168,3 @@ class ParamGen:
|
|
|
173
168
|
return np.arange(val["start"], val["stop"], step).tolist()
|
|
174
169
|
|
|
175
170
|
return [val]
|
|
176
|
-
|
|
177
|
-
def _apply_updates(
|
|
178
|
-
self, cfg: Dict[str, Any], updates: Dict[str, Any]
|
|
179
|
-
) -> Dict[str, Any]:
|
|
180
|
-
"""Deep merges dot-notation updates into cfg."""
|
|
181
|
-
for key, val in updates.items():
|
|
182
|
-
parts = key.split(".")
|
|
183
|
-
target = cfg
|
|
184
|
-
for part in parts[:-1]:
|
|
185
|
-
if part not in target:
|
|
186
|
-
target[part] = {}
|
|
187
|
-
target = target[part]
|
|
188
|
-
target[parts[-1]] = val
|
|
189
|
-
return cfg
|
|
@@ -210,7 +210,37 @@ class PerfCalc(ABC): # Abstract base class for performance calculation
|
|
|
210
210
|
**kwargs,
|
|
211
211
|
) -> Tuple[Union[List[OrderedDict], pd.DataFrame], Optional[str]]:
|
|
212
212
|
"""
|
|
213
|
-
|
|
213
|
+
Orchestrates the calculation of performance metrics and saves the results to a CSV.
|
|
214
|
+
|
|
215
|
+
This method acts as a pipeline:
|
|
216
|
+
1. Calculates metrics (Accuracy, F1, etc.) using `raw_metrics_data`.
|
|
217
|
+
2. Merges in `extra_data` (metadata) to the results.
|
|
218
|
+
3. Saves the combined data to a CSV file.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
raw_metrics_data (Union[List[dict], dict]):
|
|
222
|
+
The input data required for metric calculations.
|
|
223
|
+
Structure: A dictionary where keys are metric names (e.g., 'accuracy')
|
|
224
|
+
and values are inputs (e.g., {'preds': ..., 'target': ...}).
|
|
225
|
+
|
|
226
|
+
extra_data (Optional[Union[List[dict], dict]]):
|
|
227
|
+
Static metadata or context to attach to the result rows.
|
|
228
|
+
These fields are NOT used for calculation but are passed through
|
|
229
|
+
to the final output (DataFrame/CSV).
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
If extra_data={'model_version': 'v1.0', 'epoch': 5},
|
|
233
|
+
the output CSV will include columns 'model_version' and 'epoch'.
|
|
234
|
+
|
|
235
|
+
*args, **kwargs:
|
|
236
|
+
Additional arguments passed to `calc_exp_perf_metrics` or `save_results_to_csv`
|
|
237
|
+
(e.g., `outfile`, `return_df`).
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Tuple[Union[List[OrderedDict], pd.DataFrame], Optional[str]]:
|
|
241
|
+
A tuple containing:
|
|
242
|
+
1. The results as a DataFrame (if return_df=True) or a list of dicts.
|
|
243
|
+
2. The path to the saved output file (or None if save failed).
|
|
214
244
|
"""
|
|
215
245
|
metric_names = self.get_metric_backend().metric_names
|
|
216
246
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from typing import Dict, Any, Callable, Optional
|
|
2
|
+
from rich.pretty import pprint
|
|
3
|
+
|
|
4
|
+
class DictUtils:
|
|
5
|
+
"""
|
|
6
|
+
General-purpose dictionary manipulation utilities.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def flatten(
|
|
11
|
+
d: Dict[str, Any],
|
|
12
|
+
parent_key: str = "",
|
|
13
|
+
sep: str = ".",
|
|
14
|
+
is_leaf_predicate: Optional[Callable[[Any], bool]] = None,
|
|
15
|
+
) -> Dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Recursively flattens a nested dictionary.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
d: The dictionary to flatten.
|
|
21
|
+
parent_key: Prefix for keys (used during recursion).
|
|
22
|
+
sep: Separator for dot-notation keys.
|
|
23
|
+
is_leaf_predicate: Optional function that returns True if a value should
|
|
24
|
+
be treated as a leaf (value) rather than a branch to recurse.
|
|
25
|
+
Useful if you have dicts you don't want flattened.
|
|
26
|
+
"""
|
|
27
|
+
items = []
|
|
28
|
+
for k, v in d.items():
|
|
29
|
+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
|
30
|
+
|
|
31
|
+
# Check if we should treat this as a leaf (custom logic)
|
|
32
|
+
if is_leaf_predicate and is_leaf_predicate(v):
|
|
33
|
+
items.append((new_key, v))
|
|
34
|
+
# Standard recursion
|
|
35
|
+
elif isinstance(v, dict):
|
|
36
|
+
items.extend(
|
|
37
|
+
DictUtils.flatten(
|
|
38
|
+
v, new_key, sep=sep, is_leaf_predicate=is_leaf_predicate
|
|
39
|
+
).items()
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
items.append((new_key, v))
|
|
43
|
+
return dict(items)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def unflatten(flat_dict: Dict[str, Any], sep: str = ".") -> Dict[str, Any]:
|
|
47
|
+
"""
|
|
48
|
+
Converts flat dot-notation keys back to nested dictionaries.
|
|
49
|
+
e.g., {'a.b': 1} -> {'a': {'b': 1}}
|
|
50
|
+
"""
|
|
51
|
+
nested = {}
|
|
52
|
+
for key, value in flat_dict.items():
|
|
53
|
+
DictUtils.deep_set(nested, key, value, sep=sep)
|
|
54
|
+
return nested
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def deep_update(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Recursively merges 'update' dict into 'base' dict.
|
|
60
|
+
|
|
61
|
+
Unlike the standard `dict.update()`, which replaces nested dictionaries entirely,
|
|
62
|
+
this method enters nested dictionaries and updates them key-by-key. This preserves
|
|
63
|
+
existing keys in 'base' that are not present in 'update'.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
base: The original dictionary to modify.
|
|
67
|
+
update: The dictionary containing new values.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The modified 'base' dictionary.
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> base = {'model': {'name': 'v1', 'dropout': 0.5}}
|
|
74
|
+
>>> new_vals = {'model': {'name': 'v2'}}
|
|
75
|
+
>>> # Standard update would delete 'dropout'. deep_update keeps it:
|
|
76
|
+
>>> DictUtils.deep_update(base, new_vals)
|
|
77
|
+
{'model': {'name': 'v2', 'dropout': 0.5}}
|
|
78
|
+
"""
|
|
79
|
+
for k, v in update.items():
|
|
80
|
+
if isinstance(v, dict) and k in base and isinstance(base[k], dict):
|
|
81
|
+
DictUtils.deep_update(base[k], v)
|
|
82
|
+
else:
|
|
83
|
+
base[k] = v
|
|
84
|
+
return base
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def deep_set(d: Dict[str, Any], dot_key: str, value: Any, sep: str = ".") -> None:
|
|
88
|
+
"""
|
|
89
|
+
Sets a value in a nested dictionary using a dot-notation key path.
|
|
90
|
+
Automatically creates any missing intermediate dictionaries.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
d: The dictionary to modify.
|
|
94
|
+
dot_key: The path to the value (e.g., "model.backbone.layers").
|
|
95
|
+
value: The value to set.
|
|
96
|
+
sep: The separator used in the key (default is ".").
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> cfg = {}
|
|
100
|
+
>>> DictUtils.deep_set(cfg, "a.b.c", 10)
|
|
101
|
+
>>> print(cfg)
|
|
102
|
+
{'a': {'b': {'c': 10}}}
|
|
103
|
+
"""
|
|
104
|
+
parts = dot_key.split(sep)
|
|
105
|
+
target = d
|
|
106
|
+
for part in parts[:-1]:
|
|
107
|
+
if part not in target:
|
|
108
|
+
target[part] = {}
|
|
109
|
+
target = target[part]
|
|
110
|
+
if not isinstance(target, dict):
|
|
111
|
+
# Handle conflict if a path was previously a value (e.g. overwriting a leaf)
|
|
112
|
+
target = {}
|
|
113
|
+
target[parts[-1]] = value
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
# --- Setup ---
|
|
118
|
+
base_config = {
|
|
119
|
+
"model": {
|
|
120
|
+
"name": "ResNet50",
|
|
121
|
+
"layers": 50,
|
|
122
|
+
"details": {
|
|
123
|
+
"activation": "relu",
|
|
124
|
+
"dropout": 0.5, # <--- We want to keep this
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
"epochs": 10,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
new_settings = {
|
|
131
|
+
"model": {"details": {"activation": "gelu"}} # <--- We only want to change this
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
b1 = base_config.copy()
|
|
135
|
+
b2 = base_config.copy()
|
|
136
|
+
n1 = new_settings.copy()
|
|
137
|
+
n2 = new_settings.copy()
|
|
138
|
+
|
|
139
|
+
pprint("Base Config:")
|
|
140
|
+
pprint(base_config)
|
|
141
|
+
pprint("New Settings:")
|
|
142
|
+
pprint(new_settings)
|
|
143
|
+
print("*" * 40)
|
|
144
|
+
pprint(
|
|
145
|
+
"Task: Update base_config with new_settings, preserving unspecified nested keys."
|
|
146
|
+
)
|
|
147
|
+
print("*" * 40)
|
|
148
|
+
|
|
149
|
+
# --- Standard Update (The Problem) ---
|
|
150
|
+
pprint("Normal Update Result:")
|
|
151
|
+
b1.update(n1)
|
|
152
|
+
pprint(b1) # type: ignore[return-value]
|
|
153
|
+
|
|
154
|
+
# --- Deep Update (The Solution) ---
|
|
155
|
+
pprint("Deep Update Result:")
|
|
156
|
+
pprint(DictUtils.deep_update(b2, n2))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: halib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
4
4
|
Summary: Small library for common tasks
|
|
5
5
|
Author: Hoang Van Ha
|
|
6
6
|
Author-email: hoangvanhauit@gmail.com
|
|
@@ -56,7 +56,11 @@ Dynamic: summary
|
|
|
56
56
|
|
|
57
57
|
## v0.2.x (Experiment & Core Updates)
|
|
58
58
|
|
|
59
|
-
### **v0.2.
|
|
59
|
+
### **v0.2.23**
|
|
60
|
+
- ✨ **New Feature:** Added `utils.dict.DictUtils` for advanced dictionary manipulations (merging, filtering, transforming).
|
|
61
|
+
|
|
62
|
+
- ✨ **New Feature:** Added `common.common.pprint_stack_trace` to print stack traces with optional custom messages and force stop capability.
|
|
63
|
+
|
|
60
64
|
- 🚀 **Improvement:** `exp.perf.profiler` - allow to export *report dict* as csv files for further analysis
|
|
61
65
|
|
|
62
66
|
### **v0.2.19**
|
halib-0.2.20/halib/utils/dict.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|