# misctools_utils.py
import numpy as np
from collections import defaultdict
from typing import Any, Optional, Dict, List
import sys
import pandas as pd
from pathlib import Path
from typing import Union
import os
# Try to import required modules at module level
try:
from clabtoolkit import cltcolors
HAS_CLTCOLORS = True
except ImportError:
HAS_CLTCOLORS = False
try:
from IPython.display import display, HTML
from IPython import get_ipython
HAS_IPYTHON = True
except ImportError:
HAS_IPYTHON = False
[docs]
class ExplorerDict(dict):
"""
Enhanced dictionary with built-in exploration and visualization methods.
Works like a regular dict but adds methods for tree visualization,
summaries, searching, and safe updating.
Examples
--------
>>> data = ExplorerDict({
... 'subject': 'sub-001',
... 'metadata': {'age': 25, 'sessions': ['ses-01', 'ses-02']}
... })
>>> data.tree() # Print tree visualization
>>> data.summary() # Get statistics
>>> data.search('age') # Find keys
"""
[docs]
def __init__(
self, *args, name: str = "root", force_mode: Optional[str] = None, **kwargs
):
"""
Initialize ExplorerDict.
Parameters
----------
*args, **kwargs
Same as dict initialization
name : str
Name for the root object (for display)
force_mode : str, optional
Force display mode: 'notebook', 'terminal', or None (auto-detect)
"""
super().__init__(*args, **kwargs)
self._name = name
if force_mode is not None:
self._notebook_mode = force_mode == "notebook"
else:
self._notebook_mode = self._is_notebook()
if self._notebook_mode and not HAS_IPYTHON:
self._notebook_mode = False
@staticmethod
def _is_notebook():
"""Check if running in Jupyter notebook."""
if not HAS_IPYTHON:
return False
try:
shell = get_ipython().__class__.__name__
return shell == "ZMQInteractiveShell"
except (NameError, AttributeError):
return False
###########################################################################
[docs]
def to_dataframe(
self,
exclude_keys: Optional[List[str]] = None,
include_keys: Optional[List[str]] = None,
) -> pd.DataFrame:
"""
Convert dictionary to pandas DataFrame.
Returns
-------
pd.DataFrame
DataFrame representation of the dictionary
Examples
--------
>>> data = ExplorerDict({'a': 1, 'b': 2})
>>> df = data.to_dataframe()
"""
try:
import pandas as pd
except ImportError:
raise ImportError("pandas is required for to_dataframe method.")
# Create a copy to avoid modifying the original
# and filter based on include/exclude keys
if exclude_keys:
data_copy = {k: v for k, v in self.items() if k not in exclude_keys}
return pd.DataFrame(data_copy)
if include_keys:
data_copy = {k: v for k, v in self.items() if k in include_keys}
return pd.DataFrame(data_copy)
return pd.DataFrame(self)
############################################################################
[docs]
def tree(
self,
max_depth: Optional[int] = None,
max_items: int = 10,
max_str_len: int = 50,
show_types: bool = True,
show_shapes: bool = True,
) -> None:
"""
Print tree visualization of the dictionary structure.
Parameters
----------
max_depth : int, optional
Maximum depth to display
max_items : int
Maximum items to show per level
max_str_len : int
Maximum string length before truncation
show_types : bool
Show type information
show_shapes : bool
Show array shapes
Examples
--------
>>> data = ExplorerDict({'a': [1, 2, 3], 'b': {'c': 4}})
>>> data.tree()
🌳 root
├── a: [1, 2, 3] (list, len=3)
└── b: (dict, len=1)
└── c: 4 (int)
"""
if self._notebook_mode:
self._tree_html(max_depth, max_items, max_str_len, show_types, show_shapes)
else:
self._tree_terminal(
max_depth, max_items, max_str_len, show_types, show_shapes
)
############################################################################
def _tree_terminal(
self, max_depth, max_items, max_str_len, show_types, show_shapes
):
"""Terminal output with ANSI colors."""
if HAS_CLTCOLORS:
print(
f"{cltcolors.bcolors.HEADER}{cltcolors.bcolors.BOLD}🌳 {self._name}{cltcolors.bcolors.ENDC}"
)
print(f"{cltcolors.bcolors.OKGRAY}{'─' * 60}{cltcolors.bcolors.ENDC}")
else:
print(f"🌳 {self._name}")
print("─" * 60)
self._print_tree_terminal(
self,
"",
True,
max_depth,
0,
max_items,
max_str_len,
show_types,
show_shapes,
)
print()
############################################################################
def _print_tree_terminal(
self,
obj,
indent,
is_last,
max_depth,
depth,
max_items,
max_str_len,
show_types,
show_shapes,
):
"""Recursive tree printer for terminal."""
if max_depth is not None and depth >= max_depth:
connector = "└── " if is_last else "├── "
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKGRAY}{connector}<max depth>{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}{connector}<max depth>")
return
connector = "└── " if is_last else "├── "
extension = " " if is_last else "│ "
if isinstance(obj, dict):
items = list(obj.items())[:max_items]
truncated = len(obj) > max_items
for i, (key, value) in enumerate(items):
is_last_item = (i == len(items) - 1) and not truncated
type_info = self._get_type_info(value, show_types, show_shapes)
if isinstance(value, (dict, list, tuple)):
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKWHITE}{connector}"
f"{cltcolors.bcolors.DARKCYAN}{key}{cltcolors.bcolors.ENDC}: "
f"{cltcolors.bcolors.OKGRAY}{type_info}{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}{connector}{key}: {type_info}")
self._print_tree_terminal(
value,
indent + extension,
is_last_item,
max_depth,
depth + 1,
max_items,
max_str_len,
show_types,
show_shapes,
)
else:
val_str = self._format_value(value, max_str_len)
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKWHITE}{connector}"
f"{cltcolors.bcolors.DARKCYAN}{key}{cltcolors.bcolors.ENDC}: "
f"{cltcolors.bcolors.OKPURPLE}{val_str}{cltcolors.bcolors.ENDC} "
f"{cltcolors.bcolors.OKGRAY}{type_info}{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}{connector}{key}: {val_str} {type_info}")
if truncated:
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKGRAY}└── "
f"... [{len(obj) - max_items} more items]{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}└── ... [{len(obj) - max_items} more items]")
elif isinstance(obj, (list, tuple)):
items = obj[:max_items]
truncated = len(obj) > max_items
for i, item in enumerate(items):
is_last_item = (i == len(items) - 1) and not truncated
type_info = self._get_type_info(item, show_types, show_shapes)
if isinstance(item, (dict, list, tuple)):
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKWHITE}{connector}"
f"{cltcolors.bcolors.OKYELLOW}[{i}]{cltcolors.bcolors.ENDC}: "
f"{cltcolors.bcolors.OKGRAY}{type_info}{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}{connector}[{i}]: {type_info}")
self._print_tree_terminal(
item,
indent + extension,
is_last_item,
max_depth,
depth + 1,
max_items,
max_str_len,
show_types,
show_shapes,
)
else:
val_str = self._format_value(item, max_str_len)
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKWHITE}{connector}"
f"{cltcolors.bcolors.OKYELLOW}[{i}]{cltcolors.bcolors.ENDC}: "
f"{cltcolors.bcolors.OKPURPLE}{val_str}{cltcolors.bcolors.ENDC} "
f"{cltcolors.bcolors.OKGRAY}{type_info}{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}{connector}[{i}]: {val_str} {type_info}")
if truncated:
if HAS_CLTCOLORS:
print(
f"{indent}{cltcolors.bcolors.OKGRAY}└── "
f"... [{len(obj) - max_items} more items]{cltcolors.bcolors.ENDC}"
)
else:
print(f"{indent}└── ... [{len(obj) - max_items} more items]")
############################################################################
def _tree_html(self, max_depth, max_items, max_str_len, show_types, show_shapes):
"""Notebook output with HTML formatting - Dark theme."""
if not HAS_IPYTHON:
# Fallback to terminal
self._tree_terminal(
max_depth, max_items, max_str_len, show_types, show_shapes
)
return
html = f"""
<div style="font-family: 'Courier New', monospace; line-height: 1.4;
background: #1e1e1e; color: #d4d4d4; padding: 15px;
border-radius: 5px; border: 1px solid #3c3c3c;">
<h3 style="color: #c586c0; margin: 0 0 10px 0; font-weight: normal;">🌳 {self._name}</h3>
<div style="border-top: 2px solid #3c3c3c; padding-top: 10px;">
"""
html += self._build_tree_html(
self,
"",
True,
max_depth,
0,
max_items,
max_str_len,
show_types,
show_shapes,
)
html += """
</div>
</div>
"""
display(HTML(html))
############################################################################
def _build_tree_html(
self,
obj,
indent,
is_last,
max_depth,
depth,
max_items,
max_str_len,
show_types,
show_shapes,
):
"""Recursively build HTML tree - Dark theme."""
html = ""
if max_depth is not None and depth >= max_depth:
connector = "└── " if is_last else "├── "
html += f'{indent}<span style="color: #6a6a6a;">{connector}<max depth></span><br>'
return html
connector = "└── " if is_last else "├── "
extension_char = (
" " if is_last else "│ "
)
if isinstance(obj, dict):
items = list(obj.items())[:max_items]
truncated = len(obj) > max_items
for i, (key, value) in enumerate(items):
is_last_item = (i == len(items) - 1) and not truncated
type_info = self._get_type_info(value, show_types, show_shapes)
if isinstance(value, (dict, list, tuple)):
html += (
f'{indent}<span style="color: #858585;">{connector}</span>'
f'<span style="color: #4ec9b0; font-weight: bold;">{key}</span>: '
f'<span style="color: #808080;">{type_info}</span><br>'
)
html += self._build_tree_html(
value,
indent + extension_char,
is_last_item,
max_depth,
depth + 1,
max_items,
max_str_len,
show_types,
show_shapes,
)
else:
val_str = self._format_value(value, max_str_len)
html += (
f'{indent}<span style="color: #858585;">{connector}</span>'
f'<span style="color: #4ec9b0; font-weight: bold;">{key}</span>: '
f'<span style="color: #ce9178;">{val_str}</span> '
f'<span style="color: #808080;">{type_info}</span><br>'
)
if truncated:
html += (
f'{indent}<span style="color: #6a6a6a;">└── '
f"... [{len(obj) - max_items} more items]</span><br>"
)
elif isinstance(obj, (list, tuple)):
items = obj[:max_items]
truncated = len(obj) > max_items
for i, item in enumerate(items):
is_last_item = (i == len(items) - 1) and not truncated
type_info = self._get_type_info(item, show_types, show_shapes)
if isinstance(item, (dict, list, tuple)):
html += (
f'{indent}<span style="color: #858585;">{connector}</span>'
f'<span style="color: #dcdcaa;">[{i}]</span>: '
f'<span style="color: #808080;">{type_info}</span><br>'
)
html += self._build_tree_html(
item,
indent + extension_char,
is_last_item,
max_depth,
depth + 1,
max_items,
max_str_len,
show_types,
show_shapes,
)
else:
val_str = self._format_value(item, max_str_len)
html += (
f'{indent}<span style="color: #858585;">{connector}</span>'
f'<span style="color: #dcdcaa;">[{i}]</span>: '
f'<span style="color: #ce9178;">{val_str}</span> '
f'<span style="color: #808080;">{type_info}</span><br>'
)
if truncated:
html += (
f'{indent}<span style="color: #6a6a6a;">└── '
f"... [{len(obj) - max_items} more items]</span><br>"
)
return html
############################################################################
def _get_type_info(self, obj, show_types, show_shapes):
"""Get formatted type and shape information."""
if not show_types and not show_shapes:
return ""
info_parts = []
if isinstance(obj, np.ndarray):
if show_shapes:
info_parts.append(f"shape={obj.shape}")
if show_types:
info_parts.append(f"dtype={obj.dtype}")
elif isinstance(obj, (list, tuple)):
if show_types:
info_parts.append(f"{type(obj).__name__}")
info_parts.append(f"len={len(obj)}")
elif isinstance(obj, dict):
if show_types:
info_parts.append("dict")
info_parts.append(f"len={len(obj)}")
elif show_types:
info_parts.append(f"{type(obj).__name__}")
return f"({', '.join(info_parts)})" if info_parts else ""
############################################################################
def _format_value(self, value, max_len):
"""Format a value for display."""
if isinstance(value, np.ndarray):
if value.size == 0:
return "empty array"
elif value.size <= 5:
return str(value)
else:
return f"array([{value.flat[0]}, ..., {value.flat[-1]}])"
val_str = str(value)
if len(val_str) > max_len:
return val_str[:max_len] + "..."
return val_str
############################################################################
[docs]
def summary(self, verbose: bool = False) -> Dict[str, Any]:
"""
Get comprehensive summary statistics.
Parameters
----------
verbose : bool
Show detailed information
Returns
-------
dict
Statistics about the dictionary structure
Examples
--------
>>> data = ExplorerDict({'a': [1, 2], 'b': {'c': 3}})
>>> stats = data.summary()
📊 Dictionary Summary: root
...
"""
if self._notebook_mode:
return self._summary_html(verbose)
else:
return self._summary_terminal(verbose)
############################################################################
def _summary_terminal(self, verbose):
"""Terminal summary with ANSI colors."""
stats = self._collect_stats(self)
if HAS_CLTCOLORS:
print(
f"{cltcolors.bcolors.HEADER}{cltcolors.bcolors.BOLD}{'='*60}{cltcolors.bcolors.ENDC}"
)
print(
f"{cltcolors.bcolors.HEADER}{cltcolors.bcolors.BOLD}📊 Dictionary Summary: {self._name}{cltcolors.bcolors.ENDC}"
)
print(
f"{cltcolors.bcolors.HEADER}{cltcolors.bcolors.BOLD}{'='*60}{cltcolors.bcolors.ENDC}"
)
print(
f"{cltcolors.bcolors.OKBLUE}Root type:{cltcolors.bcolors.ENDC} {type(self).__name__}"
)
print(
f"{cltcolors.bcolors.OKBLUE}Top-level keys:{cltcolors.bcolors.ENDC} {len(self)}"
)
print(
f"\n{cltcolors.bcolors.OKGREEN}{cltcolors.bcolors.BOLD}Structure:{cltcolors.bcolors.ENDC}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Total dictionaries:{cltcolors.bcolors.ENDC} {stats['n_dicts']}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Total lists/tuples:{cltcolors.bcolors.ENDC} {stats['n_lists']}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Total keys:{cltcolors.bcolors.ENDC} {stats['n_keys']}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Maximum depth:{cltcolors.bcolors.ENDC} {stats['max_depth']}"
)
print(
f"\n{cltcolors.bcolors.OKGREEN}{cltcolors.bcolors.BOLD}Data types found:{cltcolors.bcolors.ENDC}"
)
for dtype, count in sorted(stats["types"].items(), key=lambda x: -x[1])[
:10
]:
print(
f" {cltcolors.bcolors.OKPURPLE}{dtype}:{cltcolors.bcolors.ENDC} {count}"
)
if stats["arrays"]:
print(
f"\n{cltcolors.bcolors.OKGREEN}{cltcolors.bcolors.BOLD}NumPy arrays:{cltcolors.bcolors.ENDC}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Count:{cltcolors.bcolors.ENDC} {len(stats['arrays'])}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Total elements:{cltcolors.bcolors.ENDC} {sum(a['size'] for a in stats['arrays'])}"
)
if verbose:
shapes = [a["shape"] for a in stats["arrays"][:5]]
shapes_str = ", ".join(str(s) for s in shapes)
if len(stats["arrays"]) > 5:
shapes_str += ", ..."
print(
f" {cltcolors.bcolors.DARKCYAN}Shapes:{cltcolors.bcolors.ENDC} {shapes_str}"
)
print(
f"\n{cltcolors.bcolors.OKGREEN}{cltcolors.bcolors.BOLD}Memory:{cltcolors.bcolors.ENDC}"
)
print(
f" {cltcolors.bcolors.DARKCYAN}Estimated size:{cltcolors.bcolors.ENDC} {self._format_bytes(stats['memory'])}"
)
print(
f"{cltcolors.bcolors.HEADER}{cltcolors.bcolors.BOLD}{'='*60}{cltcolors.bcolors.ENDC}\n"
)
else:
print("=" * 60)
print(f"📊 Dictionary Summary: {self._name}")
print("=" * 60)
print(f"Root type: {type(self).__name__}")
print(f"Top-level keys: {len(self)}")
print(f"\nStructure:")
print(f" Total dictionaries: {stats['n_dicts']}")
print(f" Total lists/tuples: {stats['n_lists']}")
print(f" Total keys: {stats['n_keys']}")
print(f" Maximum depth: {stats['max_depth']}")
print(f"\nData types found:")
for dtype, count in sorted(stats["types"].items(), key=lambda x: -x[1])[
:10
]:
print(f" {dtype}: {count}")
if stats["arrays"]:
print(f"\nNumPy arrays:")
print(f" Count: {len(stats['arrays'])}")
print(f" Total elements: {sum(a['size'] for a in stats['arrays'])}")
print(f"\nMemory:")
print(f" Estimated size: {self._format_bytes(stats['memory'])}")
print("=" * 60 + "\n")
return stats
############################################################################
def _summary_html(self, verbose):
"""Notebook summary with HTML formatting - Dark theme."""
if not HAS_IPYTHON:
return self._summary_terminal(verbose)
stats = self._collect_stats(self)
html = f"""
<div style="font-family: 'Courier New', monospace; background: #1e1e1e;
color: #d4d4d4; padding: 20px; border-radius: 5px;
border: 1px solid #3c3c3c;">
<h3 style="color: #c586c0; border-bottom: 2px solid #3c3c3c;
padding-bottom: 10px; font-weight: normal;">
📊 Dictionary Summary: {self._name}
</h3>
<p><strong style="color: #4fc1ff;">Root type:</strong> <span style="color: #d4d4d4;">{type(self).__name__}</span></p>
<p><strong style="color: #4fc1ff;">Top-level keys:</strong> <span style="color: #d4d4d4;">{len(self)}</span></p>
<h4 style="color: #4ec9b0; margin-top: 20px; font-weight: normal;">Structure</h4>
<ul style="list-style: none; padding-left: 0;">
<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Total dictionaries:</span> <span style="color: #d4d4d4;">{stats['n_dicts']}</span></li>
<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Total lists/tuples:</span> <span style="color: #d4d4d4;">{stats['n_lists']}</span></li>
<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Total keys:</span> <span style="color: #d4d4d4;">{stats['n_keys']}</span></li>
<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Maximum depth:</span> <span style="color: #d4d4d4;">{stats['max_depth']}</span></li>
</ul>
<h4 style="color: #4ec9b0; font-weight: normal;">Data types found</h4>
<ul style="list-style: none; padding-left: 0;">
"""
for dtype, count in sorted(stats["types"].items(), key=lambda x: -x[1])[:10]:
html += f'<li><span style="color: #c586c0;">â–ª</span> <strong style="color: #dcdcaa;">{dtype}:</strong> <span style="color: #d4d4d4;">{count}</span></li>'
html += "</ul>"
if stats["arrays"]:
total_elements = sum(a["size"] for a in stats["arrays"])
html += f"""
<h4 style="color: #4ec9b0; font-weight: normal;">NumPy arrays</h4>
<ul style="list-style: none; padding-left: 0;">
<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Count:</span> <span style="color: #d4d4d4;">{len(stats['arrays'])}</span></li>
<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Total elements:</span> <span style="color: #d4d4d4;">{total_elements:,}</span></li>
"""
if verbose:
shapes = [a["shape"] for a in stats["arrays"][:5]]
shapes_str = ", ".join(str(s) for s in shapes)
if len(stats["arrays"]) > 5:
shapes_str += ", ..."
html += f'<li><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Shapes:</span> <span style="color: #d4d4d4;">{shapes_str}</span></li>'
html += "</ul>"
html += f"""
<h4 style="color: #4ec9b0; font-weight: normal;">Memory</h4>
<p><span style="color: #569cd6;">â–ª</span> <span style="color: #9cdcfe;">Estimated size:</span> <strong style="color: #d4d4d4;">{self._format_bytes(stats['memory'])}</strong></p>
</div>
"""
display(HTML(html))
return stats
############################################################################
def _collect_stats(self, obj, depth=0):
"""Collect detailed statistics recursively."""
stats = defaultdict(int)
stats["max_depth"] = depth
stats["types"] = defaultdict(int)
stats["arrays"] = []
stats["memory"] = 0
def recurse(o, d):
stats["max_depth"] = max(stats["max_depth"], d)
type_name = type(o).__name__
stats["types"][type_name] += 1
if isinstance(o, dict):
stats["n_dicts"] += 1
stats["n_keys"] += len(o)
for v in o.values():
recurse(v, d + 1)
elif isinstance(o, (list, tuple)):
stats["n_lists"] += 1
for item in o:
recurse(item, d + 1)
elif isinstance(o, np.ndarray):
stats["arrays"].append(
{"shape": o.shape, "dtype": o.dtype, "size": o.size}
)
stats["memory"] += o.nbytes
elif isinstance(o, str):
stats["memory"] += len(o)
recurse(obj, depth)
return dict(stats)
############################################################################
@staticmethod
def _format_bytes(bytes_size):
"""Format bytes to human-readable string."""
for unit in ["B", "KB", "MB", "GB"]:
if bytes_size < 1024:
return f"{bytes_size:.2f} {unit}"
bytes_size /= 1024
return f"{bytes_size:.2f} TB"
############################################################################
[docs]
def structure(self, max_depth: Optional[int] = 3) -> Dict:
"""
Get simplified structure showing types and shapes.
Parameters
----------
max_depth : int, optional
Maximum depth to explore
Returns
-------
dict
Simplified structure representation
Examples
--------
>>> data = ExplorerDict({'a': [1, 2], 'b': {'c': np.array([3, 4])}})
>>> data.structure(max_depth=2)
{'a': ['<int>'], 'b': {'c': '<ndarray: shape=(2,), dtype=int64>'}}
"""
return self._build_structure(self, max_depth, 0)
############################################################################
def _build_structure(self, obj, max_depth, depth):
"""Recursively build structure representation."""
if max_depth is not None and depth >= max_depth:
return self._type_repr(obj)
if isinstance(obj, dict):
return {
key: self._build_structure(val, max_depth, depth + 1)
for key, val in obj.items()
}
elif isinstance(obj, (list, tuple)):
if len(obj) == 0:
return f"<empty {type(obj).__name__}>"
if len(obj) <= 3:
return [
self._build_structure(item, max_depth, depth + 1) for item in obj
]
else:
return [
self._build_structure(obj[0], max_depth, depth + 1),
f"<... {len(obj) - 2} more items ...>",
self._build_structure(obj[-1], max_depth, depth + 1),
]
else:
return self._type_repr(obj)
############################################################################
def _type_repr(self, obj):
"""Get type representation for leaf nodes."""
if isinstance(obj, np.ndarray):
return f"<ndarray: shape={obj.shape}, dtype={obj.dtype}>"
return f"<{type(obj).__name__}>"
############################################################################
[docs]
def search(self, pattern: str, case_sensitive: bool = False) -> List[str]:
"""Search for keys matching a pattern."""
matches = []
def recurse(obj, path=""):
if isinstance(obj, dict):
for key, value in obj.items():
key_str = str(key)
current_path = f"{path}.{key}" if path else key
if case_sensitive:
match = pattern in key_str
else:
match = pattern.lower() in key_str.lower()
if match:
matches.append(current_path)
recurse(value, current_path)
elif isinstance(obj, (list, tuple)):
for i, item in enumerate(obj):
recurse(item, f"{path}[{i}]")
recurse(self)
if self._notebook_mode and HAS_IPYTHON:
if matches:
html = f"""<div style="font-family: monospace; background: #1e1e1e;
color: #d4d4d4; padding: 15px; border-radius: 5px;
border: 1px solid #3c3c3c;">
<strong style="color: #4ec9b0;">Found {len(matches)} matches for '{pattern}':</strong>
<ul style="padding-left: 20px;">"""
for match in matches:
html += f'<li style="color: #4ec9b0;">{match}</li>'
html += "</ul></div>"
display(HTML(html))
else:
display(
HTML(
f'<div style="color: #f48771; font-family: monospace; '
f"background: #2d1f1f; padding: 10px; border-radius: 5px; "
f"border-left: 4px solid #f48771;\">No matches found for '{pattern}'</div>"
)
)
else:
if HAS_CLTCOLORS:
if matches:
print(
f"{cltcolors.bcolors.OKGREEN}Found {len(matches)} matches for '{pattern}':{cltcolors.bcolors.ENDC}"
)
for match in matches:
print(
f" {cltcolors.bcolors.DARKCYAN}{match}{cltcolors.bcolors.ENDC}"
)
else:
print(
f"{cltcolors.bcolors.FAIL}No matches found for '{pattern}'{cltcolors.bcolors.ENDC}"
)
else:
if matches:
print(f"Found {len(matches)} matches for '{pattern}':")
for match in matches:
print(f" {match}")
else:
print(f"No matches found for '{pattern}'")
return matches
############################################################################
[docs]
def get_path(self, path: str) -> Any:
"""Get value at a specific path."""
parts = path.replace("[", ".").replace("]", "").split(".")
obj = self
for part in parts:
if not part:
continue
try:
if part.isdigit():
obj = obj[int(part)]
else:
obj = obj[part]
except (KeyError, IndexError, TypeError) as e:
if self._notebook_mode and HAS_IPYTHON:
display(
HTML(
f'<div style="color: #f48771; font-family: monospace; '
f"background: #2d1f1f; padding: 10px; border-radius: 5px; "
f"border-left: 4px solid #f48771;\">Error accessing '{path}': {e}</div>"
)
)
elif HAS_CLTCOLORS:
print(
f"{cltcolors.bcolors.FAIL}Error accessing '{path}': {e}{cltcolors.bcolors.ENDC}"
)
else:
print(f"Error accessing '{path}': {e}")
return None
return obj
##############################################################################
@staticmethod
def exist_file(path: Union[str, Path]) -> str:
"""
Check if a file exists at the given path and return the path as a string if it does.
Parameters
----------
path : str or Path
The file path to check.
Returns
-------
str
The file path as a string if the file exists, otherwise an empty string.
Examples
--------
>>> exist_file('existing_file.txt')
'existing_file.txt'
>>> exist_file('nonexistent_file.txt')
''
"""
return str(path) if os.path.isfile(path) else ""
##############################################################################
@staticmethod
def exist_dir(path: Union[str, Path]) -> str:
"""
Check if a directory exists at the given path and return the path as a string if it does.
Parameters
----------
path : str or Path
The directory path to check.
Returns
-------
str
The directory path as a string if the directory exists, otherwise an empty string.
Examples
--------
>>> exist_dir('existing_folder')
'existing_folder'
>>> exist_dir('nonexistent_folder')
''
"""
return str(path) if os.path.isdir(path) else ""