Source code for recursivenamespace.utils

from __future__ import annotations

from collections import OrderedDict
from enum import Enum
from functools import lru_cache
import re
from typing import Any, Dict, List, NamedTuple


KEY_SEP_CHAR = "."
KEY_ARRAY = "[]"


[docs] def escape_key(key: str, sep: str | None = None) -> str: sep = sep or KEY_SEP_CHAR escape_char = rf"\\{sep}" return key.replace(sep, escape_char)
[docs] def unescape_key(key: str, sep: str | None = None) -> str: sep = sep or KEY_SEP_CHAR escape_char = rf"\\{sep}" return key.replace(escape_char, sep)
@lru_cache(maxsize=8) def _compile_split_pattern(sep: str) -> re.Pattern[str]: return re.compile(rf"(?<!\\)\{sep}")
[docs] def split_key(key: str, sep: str | None = None) -> list[str]: sep = sep or KEY_SEP_CHAR return _compile_split_pattern(sep).split(key)
[docs] def join_key(parts: List[str], sep: str | None = None) -> str: sep = sep or KEY_SEP_CHAR return sep.join(parts)
[docs] class KV_Pair(NamedTuple): # NOSONAR key: str value: Any
[docs] class FlatListType(Enum): SKIP = 0 WITH_INDEX = 1 WITHOUT_INDEX = 2 WITH_SMART_INDEX = 3
# TODO(refactor): reduce cognitive complexity (~15) — recursive nested type checks
[docs] def flatten_as_dict( data: Dict[str, Any] | None, sep: str = KEY_SEP_CHAR, flat_list: bool = False, use_ordered_dict: bool = True, ) -> Dict[str, Any]: out: Dict[str, Any] = OrderedDict() if use_ordered_dict else dict() sep_len = len(sep) def flatten(obj: Any, name: str = "") -> None: if isinstance(obj, dict): for attr in obj: flatten(obj[attr], f"{name}{escape_key(attr)}{sep}") elif flat_list and isinstance(obj, list): parent_name = f"{name[:-sep_len]}{KEY_ARRAY}{sep}" # use OrderedDict to retain the order, should not use index, # the index is considered as a "key" to differentiate: for i in range(len(obj)): key = f"{parent_name}{i}{sep}" flatten(obj[i], key) else: key = name[:-sep_len] out[key] = obj if data: flatten(data) # @ret: return out
# TODO(refactor): reduce cognitive complexity (~20) — deeply nested recursive + enum branching
[docs] def flatten_as_list( data: Dict[str, Any] | None, sep: str = KEY_SEP_CHAR, flat_list_type: FlatListType = FlatListType.SKIP, ) -> List[KV_Pair]: out: List[KV_Pair] = [] out_keys: Dict[str | None, int] = {} out_ref_keys: Dict[str, str] = {} sep_len = len(sep) flat_list = flat_list_type in [ FlatListType.WITH_INDEX, FlatListType.WITHOUT_INDEX, FlatListType.WITH_SMART_INDEX, ] def flatten(obj: Any, name: str = "", ref_name: str | None = None) -> None: if isinstance(obj, dict): for attr in obj: key = f"{name}{escape_key(attr)}{sep}" # print("1.0", [key, ref_name]) flatten(obj[attr], key, ref_name) elif flat_list and isinstance(obj, list): # if ref_name is existing, means set the value to the last item of the array, # thus, the parent must refer "-1" (instead of "#" -> append): if ref_name is None or ref_name not in out_keys: parent_name = f"{name[:-sep_len]}{KEY_ARRAY}{sep}" else: t_key = out_ref_keys[ref_name] t_len = len(t_key) - 1 # -1 : exclude the sign "-". t_key += name[t_len:] parent_name = f"{t_key[:-sep_len]}{KEY_ARRAY}{sep}" # use the ordered in out-List to retain the order, should not use index, # the index is considered as a "key" to differentiate: if flat_list_type == FlatListType.WITH_INDEX: # all "indexes" will be added to output keys: for i in range(len(obj)): key = f"{parent_name}{i}{sep}" # print("2.1", [key, ref_name]) flatten(obj[i], key, ref_name) elif flat_list_type == FlatListType.WITHOUT_INDEX: # all "indexes" will be replaced by "#": for i in range(len(obj)): key = f"{parent_name}#{sep}" # print("2.2", [key, ref_name]) flatten(obj[i], key, ref_name) else: # WITH_SMART_INDEX: # all "indexes" will be replaced by (same length): # - "#" if append to end of the array # - "-1" set value to last item of the array for i in range(len(obj)): key = f"{parent_name}#{sep}" ref_key = f"{parent_name}{i}{sep}" # <-- last item. out_ref_keys[ref_key] = f"{parent_name}-1{sep}" # print("2.3", [key, ref_key]) flatten(obj[i], key, ref_key) else: key = name[:-sep_len] if ref_name not in out_keys: out_keys[ref_name] = 1 # print("3.0", [key, ref_name]) # add to result: out.append(KV_Pair(key, obj)) if data: flatten(data) # @ret: return out