Source code for dags.tree.tree_utils

"""Utilities for handling qualified names in nested dictionaries."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING

import flatten_dict as fd

if TYPE_CHECKING:
    from dags.tree.typing import FlatQNameDict, FlatTreePathDict, NestedStructureDict

# Constants for qualified names
QNAME_DELIMITER: str = "__"
_python_identifier: str = r"[a-zA-Z_\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-zA-Z0-9_\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*"  # noqa: E501

# Reducers and splitters to flatten/unflatten dicts with qualified names as keys
_qualified_name_reducer = fd.reducers.make_reducer(delimiter=QNAME_DELIMITER)  # ty: ignore[possibly-missing-attribute]
_qualified_name_splitter = fd.splitters.make_splitter(delimiter=QNAME_DELIMITER)  # ty: ignore[possibly-missing-attribute]


[docs] def qname_from_tree_path(tree_path: tuple[str, ...]) -> str: """Convert a tree path to a qualified name. Args: tree_path: A tuple of strings. Returns ------- A qualified name. """ return QNAME_DELIMITER.join(tree_path)
[docs] def tree_path_from_qname(qname: str) -> tuple[str, ...]: """Convert a qualified name to a tree path (tuple of strings). Args: qname: A qualified name. Returns ------- A tree path. """ return tuple(qname.split(QNAME_DELIMITER))
[docs] def flatten_to_qnames(nested: NestedStructureDict) -> FlatQNameDict: """Flatten a nested dictionary to a flat dictionary with qualified names as keys. Args: nested: A nested dictionary. Returns ------- A flat dictionary with qualified names as keys. """ return fd.flatten(nested, reducer=_qualified_name_reducer)
[docs] def qnames(nested: NestedStructureDict) -> list[str]: """Return a list of qualified names from the keys of the nested dictionary. Args: nested: A nested dictionary. Returns ------- A list of qualified names. """ return list(flatten_to_qnames(nested).keys())
[docs] def unflatten_from_qnames(flat_qnames: FlatQNameDict) -> NestedStructureDict: """Return a nested dictionary from a flat dictionary with qualified names as keys. Args: flat_qnames: A dictionary with qualified names as keys. Returns ------- A nested dictionary. """ return fd.unflatten(flat_qnames, splitter=_qualified_name_splitter)
[docs] def flatten_to_tree_paths(nested: NestedStructureDict) -> FlatTreePathDict: """Flatten a nested dictionary to a flat dictionary with tree paths as keys. Args: nested: A nested dictionary. Returns ------- A flat dictionary with qualified names as keys. """ return fd.flatten(nested, reducer="tuple")
[docs] def tree_paths(nested: NestedStructureDict) -> list[tuple[str, ...]]: """Return a list of tree paths of the nested dictionary. Args: nested: A nested dictionary. Returns ------- A list of tuples. """ return list(flatten_to_tree_paths(nested).keys())
[docs] def unflatten_from_tree_paths(flat_tree_paths: FlatTreePathDict) -> NestedStructureDict: """Return a nested dictionary from a flat dictionary with tree paths as keys. Args: flat_tree_paths: A flat dictionary with tree paths (tuples) as keys. Returns ------- A nested dictionary. """ return fd.unflatten(flat_tree_paths, splitter="tuple")
def _is_python_identifier(s: str) -> bool: """Check if a string is a valid Python identifier. Args: s: String to check Returns ------- True if valid identifier, False otherwise """ return bool(re.fullmatch(_python_identifier, s))