Source code for econox.utils

# src/econox/utils.py
"""
General utility functions shared across the Econox package.
"""

from typing import Any, TypeVar, Union
from collections.abc import Mapping

# Sentinel value to distinguish "no default provided" from "default=None"
_MISSING = object()

T = TypeVar('T')

[docs] def get_from_pytree( data: Any, key: str, default: Union[T, object] = _MISSING ) -> Union[Any, T]: """ Retrieve a value from a data container, supporting both dict-style (['key']) and attribute-style (.key) access. Args: data: The container (dict, NamedTuple, PyTree, etc.). key: The key or attribute name to retrieve. default: Value to return if key is not found. If not provided, raises error. Returns: The value associated with the key, or default if not found. Raises: KeyError: If data is dict-like and key is missing (and no default). AttributeError: If data is object-like and attribute is missing (and no default). """ # 1. Try Mapping protocol (dict, etc.) # Use explicit check to avoid unintended sequence behavior if isinstance(data, Mapping): if key in data: return data[key] if default is not _MISSING: return default raise KeyError(f"Key '{key}' not found in data mapping of type {type(data).__name__}.") # 2. Try attribute access (NamedTuple, dataclass, class instance) if hasattr(data, key): return getattr(data, key) # 3. Try __getitem__ as a fallback (but be careful not to iterate) # This covers cases that have __getitem__ but are not Mappings (rare in this context but possible) if hasattr(data, "__getitem__"): try: return data[key] except (KeyError, TypeError, IndexError): pass # 4. Return default or raise error if default is not _MISSING: return default raise AttributeError( f"Could not find '{key}' in data object of type {type(data).__name__}. " f"Available keys/attributes could not be determined." )