Skip to content

Dictionaries

dictionaries #

Dict utility functions and classes supporting attribute access and dot-notation.

This module provides tools for working with dictionaries that allow: - Attribute-style access to dictionary keys - Conversion between nested and flattened (dot-notated) dictionaries - Safe access to nested dictionary or object fields - Recursive data cleaning of metadata dictionaries

Examples:

>>> d = AttrDict({"a": {"b": 1}})
>>> d.a.b
1
>>> flatten_dictionary({"a": {"b": 1}})
{'a.b': 1}
>>> expand_dictionary({"a.b": 1})
{'a': {'b': 1}}

Functions:

Name Description
cleanse_metadata

Recursively cleanse metadata dictionaries for serialization.

expand_dictionary

Expand dot-notated keys into a nested dictionary.

flatten_dictionary

Flatten a nested dictionary using dot-notation keys.

retrieve_nested_value

Retrieve a value or attribute using a dot-notation path.

AttrDict #

AttrDict(*args: typing.Any, **kwargs: typing.Any)

Bases: dict

A dictionary that supports dot-style attribute access and nested utilities.

This class extends the built-in dict to enable accessing keys as attributes and includes helpers to flatten and expand nested structures.

Examples:

>>> d = AttrDict({"x": {"y": 5}})
>>> d.x.y
5
>>> flat = d.to_flat_dict()
>>> flat
{'x.y': 5}
>>> AttrDict.from_flat_dict({"x.y": 5})
{'x': {'y': 5}}

Methods:

Name Description
from_flat_dict

Inflate a flat dict into a nested AttrDict.

to_flat_dict

Return a flattened dictionary using dot-notation keys.

Source code in src/imgtools/utils/dictionaries.py
def __init__(self, *args: Any, **kwargs: Any) -> None:
    # if empty, initialize with an empty dict
    if not args and not kwargs:
        super().__init__()
        return

    super().__init__(
        {k: attrify(v) for k, v in dict(*args, **kwargs).items()}
    )

from_flat_dict classmethod #

from_flat_dict(
    *args: typing.Any, **kwargs: typing.Any
) -> imgtools.utils.dictionaries.AttrDict

Inflate a flat dict into a nested AttrDict.

Example

AttrDict.from_flat_dict({"a.b": 1}) {'a': {'b': 1}}

Source code in src/imgtools/utils/dictionaries.py
@classmethod
def from_flat_dict(cls, *args: Any, **kwargs: Any) -> AttrDict:
    """Inflate a flat dict into a nested AttrDict.

    Example
    -------
    >>> AttrDict.from_flat_dict({"a.b": 1})
    {'a': {'b': 1}}
    """
    return cls(expand_dictionary(dict(*args, **kwargs)))

to_flat_dict #

to_flat_dict() -> dict[str, typing.Any]

Return a flattened dictionary using dot-notation keys.

Source code in src/imgtools/utils/dictionaries.py
def to_flat_dict(self) -> dict[str, Any]:
    """Return a flattened dictionary using dot-notation keys."""
    return flatten_dictionary(self)

attrify #

attrify(data: typing.Any) -> typing.Any

Recursively convert dicts to AttrDict and handle lists of dicts as well.

Source code in src/imgtools/utils/dictionaries.py
def attrify(data: Any) -> Any:
    """Recursively convert dicts to AttrDict and handle lists of dicts as well."""
    if isinstance(data, dict):
        return AttrDict(data)
    if isinstance(data, list):
        return [attrify(elem) for elem in data]
    return data

cleanse_metadata #

cleanse_metadata(metadata: typing.Any) -> typing.Any

Recursively cleanse metadata dictionaries for serialization.

Fixes applied: 1. Converts NaN values to None. 2. Cleans nested dictionaries and iterables. 3. Converts datetime.{datetime,date,time} to ISO format strings.

Parameters:

Name Type Description Default

metadata #

typing.Any

The input dictionary, list, or primitive.

required

Returns:

Type Description
typing.Any

The cleansed version of the input.

Source code in src/imgtools/utils/dictionaries.py
def cleanse_metadata(metadata: Any) -> Any:
    """Recursively cleanse metadata dictionaries for serialization.

    Fixes applied:
        1. Converts NaN values to None.
        2. Cleans nested dictionaries and iterables.
        3. Converts datetime.{datetime,date,time} to ISO format strings.

    Parameters
    ----------
    metadata : Any
        The input dictionary, list, or primitive.

    Returns
    -------
    Any
        The cleansed version of the input.
    """

    match metadata:
        case dict():
            return {k: cleanse_metadata(v) for k, v in metadata.items()}
        case collections.abc.Iterable() if not isinstance(
            metadata, (str, bytes)
        ):
            return [cleanse_metadata(v) for v in metadata]
        case float() if math.isnan(metadata):
            return None
        case datetime.datetime() | datetime.date() | datetime.time():
            return datetime_to_iso_string(metadata)
        case _:
            return metadata

    return metadata

expand_dictionary #

expand_dictionary(
    flat_dict: dict[str, typing.Any],
) -> dict[str, typing.Any]

Expand dot-notated keys into a nested dictionary.

Parameters:

Name Type Description Default

flat_dict #

dict

Dictionary with keys in dot-notation.

required

Returns:

Type Description
dict

Nested dictionary.

Examples:

>>> expand_dictionary({"a.b": 1})
{'a': {'b': 1}}
Source code in src/imgtools/utils/dictionaries.py
def expand_dictionary(flat_dict: dict[str, Any]) -> dict[str, Any]:
    """Expand dot-notated keys into a nested dictionary.

    Parameters
    ----------
    flat_dict : dict
        Dictionary with keys in dot-notation.

    Returns
    -------
    dict
        Nested dictionary.

    Examples
    --------
    >>> expand_dictionary({"a.b": 1})
    {'a': {'b': 1}}
    """
    nested_dict: dict[str, Any] = {}
    for key, value in flat_dict.items():
        parts = key.split(".")
        node = nested_dict
        for part in parts[:-1]:
            node = node.setdefault(part, {})
        node[parts[-1]] = value
    return nested_dict

flatten_dictionary #

flatten_dictionary(
    nested_dict: dict[str, typing.Any],
    parent_key_prefix: str = "",
) -> dict[str, typing.Any]

Flatten a nested dictionary using dot-notation keys.

Parameters:

Name Type Description Default

nested_dict #

dict

The nested dictionary to flatten.

required

parent_key_prefix #

str

Prefix for internal recursive use.

''

Returns:

Type Description
dict

Flattened dictionary.

Examples:

>>> flatten_dictionary({"a": {"b": 1}})
{'a.b': 1}
Source code in src/imgtools/utils/dictionaries.py
def flatten_dictionary(
    nested_dict: dict[str, Any], parent_key_prefix: str = ""
) -> dict[str, Any]:
    """Flatten a nested dictionary using dot-notation keys.

    Parameters
    ----------
    nested_dict : dict
        The nested dictionary to flatten.
    parent_key_prefix : str, optional
        Prefix for internal recursive use.

    Returns
    -------
    dict
        Flattened dictionary.

    Examples
    --------
    >>> flatten_dictionary({"a": {"b": 1}})
    {'a.b': 1}
    """
    flat_dict: dict[str, Any] = {}
    for key, value in nested_dict.items():
        full_key = f"{parent_key_prefix}.{key}" if parent_key_prefix else key
        if isinstance(value, dict):
            flat_dict.update(
                flatten_dictionary(value, parent_key_prefix=full_key)
            )
        else:
            flat_dict[full_key] = value
    return flat_dict

retrieve_nested_value #

retrieve_nested_value(
    container: typing.Any, field_path: str
) -> typing.Any | None

Retrieve a value or attribute using a dot-notation path.

Parameters:

Name Type Description Default

container #

typing.Any

The object or dictionary to access.

required

field_path #

str

Dot-notation string path.

required

Returns:

Type Description
typing.Any or None

The resolved value or None if not found.

Examples:

>>> retrieve_nested_value({"a": {"b": 1}}, "a.b")
1
Source code in src/imgtools/utils/dictionaries.py
def retrieve_nested_value(container: Any, field_path: str) -> Any | None:
    """Retrieve a value or attribute using a dot-notation path.

    Parameters
    ----------
    container : Any
        The object or dictionary to access.
    field_path : str
        Dot-notation string path.

    Returns
    -------
    Any or None
        The resolved value or None if not found.

    Examples
    --------
    >>> retrieve_nested_value({"a": {"b": 1}}, "a.b")
    1
    """
    try:
        return container[field_path]
    except (TypeError, KeyError):
        pass
    try:
        return getattr(container, field_path)
    except AttributeError as exc:
        if f"object has no attribute {field_path!r}" not in str(exc):
            raise

    node = container
    for part in field_path.split("."):
        try:
            node = node[part]
            continue
        except (TypeError, KeyError):
            pass
        try:
            node = getattr(node, part)
            continue
        except AttributeError as exc:
            if f"object has no attribute {part!r}" not in str(exc):
                raise
            return None
    return node