Source code for dictutils.qsdict
from collections.abc import Iterable, Mapping
from typing import Union
# Minimum number of arguments required (excluding first parameter)
MIN_ARGS = 2
def _extract_value(q, key, strict: bool = False):
"""Extract value from row q using selector key."""
if callable(key):
return key(q)
elif isinstance(key, int) and isinstance(q, Mapping) and key in q:
return q[key]
elif isinstance(key, str) and hasattr(q, key):
return getattr(q, key)
elif isinstance(q, Mapping) and key in q:
return q[key]
else:
if strict:
if isinstance(q, Mapping):
raise KeyError(f"Missing key {key!r}")
else:
raise AttributeError(f"Missing attribute {key!r}")
return None
def _set_leaf_value(current_dict, q, args, extract_fn):
"""Set the leaf value in the nested structure."""
if isinstance(args[-1], tuple):
# Multiple leaf values as a list
current_dict[extract_fn(q, args[-2])] = [extract_fn(q, el) for el in args[-1]]
else:
# Single leaf value
current_dict[extract_fn(q, args[-2])] = extract_fn(q, args[-1])
[docs]
def qsdict(qs: Iterable[Union[Mapping, object]], *args, strict: bool = False) -> dict:
"""
Build a nested dict from rows (dicts or objects) by a sequence of selectors.
Args:
qs: Iterable of mappings or objects to process
*args: Selectors - each can be:
- Key name (str/int) to access dict keys or object attributes
- Callable that takes a row and returns a key
- Tuple of selectors for the leaf values (last arg only)
strict: If True, raise KeyError/AttributeError for missing selectors.
If False (default), missing selectors yield None keys.
Returns:
Nested dict where each level corresponds to a selector
Examples:
>>> data = [{"cat": "A", "val": 1}, {"cat": "B", "val": 2}]
>>> qsdict(data, "cat", "val")
{'A': 1, 'B': 2}
>>> qsdict(data, lambda x: x["cat"].lower(), "val")
{'a': 1, 'b': 2}
"""
if len(args) < MIN_ARGS:
raise ValueError("Need at least two fields to nest dicts")
# Create extraction function with strict mode baked in
def extract_fn(q, key):
return _extract_value(q, key, strict)
# Use plain dict everywhere (Python 3.9+ has insertion-order guarantees)
root: dict = {}
for q in qs:
nested_dicts = [root]
# Build nested structure up to second-to-last level
for key in args[:-2]:
current_dict = nested_dicts[-1]
value = extract_fn(q, key)
if value not in current_dict:
current_dict[value] = {}
nested_dicts.append(current_dict[value])
# Set leaf value at the deepest level
current_dict = nested_dicts[-1]
_set_leaf_value(current_dict, q, args, extract_fn)
return root