dictutils
A collection of utilities for manipulating nested dictionaries and data structures in Python.
Requirements
- Python 3.9+
Installation
Install from PyPI (recommended):
pip install dictutils
Or from source (using PEP 517/518):
pip install .
# For contributors (editable install):
pip install -e .[test,typecheck,lint]
Quick Examples
from dictutils import qsdict, mergedict, pivot, nest_agg, Agg
# Build nested dicts from lists
sales = [{"region": "North", "product": "Widget", "revenue": 1000}]
result = qsdict(sales, "region", "product", "revenue")
# {"North": {"Widget": 1000}}
# Safely merge nested dicts
a = {"user": {"name": "Alice"}}
b = {"user": {"email": "alice@example.com"}}
mergedict(a, b) # {"user": {"name": "Alice", "email": "alice@example.com"}}
# Pivot nested structures
data = {"A": {"X": 1, "Y": 2}, "B": {"X": 3, "Y": 4}}
pivot(data, [1, 0]) # {"X": {"A": 1, "B": 3}, "Y": {"A": 2, "B": 4}}
# Advanced aggregation
aggs = {"total": Agg(map=lambda x: x["amount"], zero=0)}
nest_agg(transactions, keys=["category"], aggs=aggs)
API
dictutils.qsdict
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."""
Example
from dictutils import qsdict
lst = [
{"shape": "circle", "colour": "blue", "count": 5},
{"shape": "circle", "colour": "pink", "count": 15},
{"shape": "square", "colour": "yellow", "count": 29},
{"shape": "square", "colour": "blue", "count": 10},
]
result = qsdict(lst, "shape", "colour", "count")
# {'circle': {'blue': 5, 'pink': 15}, 'square': {'yellow': 29, 'blue': 10}}
# Strict mode raises on missing keys/attributes
qsdict([{"a": 1}], "a", "missing", strict=True) # KeyError
dictutils.mergedict
def mergedict(*args, path=None, update=True) -> dict:
"""Merge multiple nested dicts. The first dict is updated in-place."""
Example
from dictutils import mergedict
d1 = {"a": {"b": 1}}
d2 = {"a": {"c": 2}}
merged = mergedict(d1, d2)
# d1 is now {"a": {"b": 1, "c": 2}}
dictutils.pivot
def pivot(d: dict, order: list[int]) -> dict:
"""Pivot a nested dict by a list of key indices."""
Example
from dictutils import pivot
d = {"A": {"X": 1, "Y": 2}, "B": {"X": 3, "Y": 4}}
result = pivot(d, [1, 0])
# {'X': {'A': 1, 'B': 3}, 'Y': {'A': 2, 'B': 4}}
dictutils.nestagg
from typing import Any, Callable
from dataclasses import dataclass
def nest_agg(
items: list[Any],
keys: list[str | Callable[[Any], Any]],
*,
aggs: dict[str, 'Agg'],
include_rows: bool = False,
rows_key: str = "rows",
) -> dict:
"""Group and aggregate items by keys, with flexible aggregation at leaves."""
@dataclass(frozen=True)
class Agg:
map: Callable[[Any], Any]
zero: Any | Callable[[], Any] | None = None
reduce: Callable[[Any, Any], Any] = operator.add
skip_none: bool = True
finalize: Callable[[Any], Any] | None = None # Transform final result
Examples
from dictutils import nest_agg, Agg
# Simple aggregation
items = [{"cat": "A", "val": 1}, {"cat": "A", "val": 2}, {"cat": "B", "val": 3}]
aggs = {"total": Agg(map=lambda it: it["val"], zero=0)}
result = nest_agg(items, keys=["cat"], aggs=aggs)
# {'A': {'total': 3}, 'B': {'total': 3}}
# Calculate averages with finalize
aggs = {
"avg": Agg(
map=lambda x: (x["val"], 1),
zero=(0, 0),
reduce=lambda a, b: (a[0] + b[0], a[1] + b[1]),
finalize=lambda x: x[0] / x[1] if x[1] > 0 else 0
)
}
result = nest_agg(items, keys=["cat"], aggs=aggs)
# {'A': {'avg': 1.5}, 'B': {'avg': 3.0}}
dictutils.ops - Advanced Operations
The ops module provides 20+ utilities for advanced dictionary manipulation:
Path Operations
from dictutils.ops import deep_get, deep_set, deep_has, ensure_path
d = {"user": {"profile": {"name": "Alice"}}}
deep_get(d, "user.profile.name") # "Alice"
deep_set(d, "user.profile.age", 30) # Creates nested path if needed
deep_has(d, "user.settings") # False
ensure_path(d, "user.settings") # Creates empty dict at path
Data Transformation
from dictutils.ops import flatten_paths, expand_paths, pivot, transpose_dict
# Flatten nested structure to dot notation
flatten_paths({"a": {"b": 1, "c": 2}}) # {"a.b": 1, "a.c": 2}
# Expand dot notation back to nested
expand_paths({"a.b": 1, "a.c": 2}) # {"a": {"b": 1, "c": 2}}
# Transpose nested dict structure
transpose_dict({"A": {"X": 1, "Y": 2}, "B": {"X": 3, "Y": 4}})
# {"X": {"A": 1, "B": 3}, "Y": {"A": 2, "B": 4}}
Aggregation Helpers
from dictutils.ops import group_by, count_by, sum_by, index_by
users = [
{"id": 1, "name": "Alice", "dept": "eng", "salary": 90000},
{"id": 2, "name": "Bob", "dept": "eng", "salary": 85000},
{"id": 3, "name": "Carol", "dept": "sales", "salary": 70000}
]
group_by(users, "dept") # {"eng": [...], "sales": [...]}
count_by(users, "dept") # {"eng": 2, "sales": 1}
sum_by(users, "dept", lambda x: x["salary"]) # {"eng": 175000, "sales": 70000}
index_by(users, "id") # {1: {...}, 2: {...}, 3: {...}}
Real-World Examples
Sales Data Analysis
from dictutils import qsdict, nest_agg, Agg
# Convert query results to nested structure
sales = [
{"region": "North", "product": "Widget", "revenue": 1000, "units": 50},
{"region": "North", "product": "Gadget", "revenue": 1500, "units": 30},
{"region": "South", "product": "Widget", "revenue": 800, "units": 40},
]
# Group by region -> product, show revenue
by_region = qsdict(sales, "region", "product", "revenue")
# {"North": {"Widget": 1000, "Gadget": 1500}, "South": {"Widget": 800}}
# Calculate metrics with aggregation
aggs = {
"total_revenue": Agg(map=lambda x: x["revenue"], zero=0),
"avg_price": Agg(
map=lambda x: (x["revenue"], x["units"]),
zero=(0, 0),
reduce=lambda a, b: (a[0] + b[0], a[1] + b[1]),
finalize=lambda x: x[0] / x[1] if x[1] > 0 else 0
)
}
metrics = nest_agg(sales, keys=["region"], aggs=aggs)
# {"North": {"total_revenue": 2500, "avg_price": 31.25}, ...}
Configuration Management
from dictutils import mergedict
from dictutils.ops import deep_get, deep_set
# Merge multiple config sources
default_config = {"db": {"host": "localhost", "port": 5432}}
user_config = {"db": {"password": "secret"}, "cache": {"ttl": 300}}
env_config = {"db": {"host": "prod-db.example.com"}}
config = mergedict({}, default_config, user_config, env_config)
# Result: {"db": {"host": "prod-db.example.com", "port": 5432, "password": "secret"}, "cache": {"ttl": 300}}
# Access nested values safely
db_host = deep_get(config, "db.host", default="localhost")
deep_set(config, "logging.level", "INFO") # Creates nested structure
Documentation
📖 Complete Documentation - API reference, cookbook, and examples
- Quickstart Guide - Get up and running quickly
- API Reference - Complete function documentation
- Cookbook - Real-world examples and patterns
- Advanced Operations - dictutils.ops module guide
Development
- Install dev tools:
pip install .[test,typecheck,lint] - Run tests:
pytest - Type check:
mypy dictutils - Lint:
ruff dictutils - Format:
black dictutils - Upgrade syntax:
pyupgrade --py39-plus <file.py> - Pre-commit:
pre-commit install(thengit commitauto-runs checks)
Contributing
Contributions are welcome! See our documentation for development setup and coding standards.
Migration from 0.1.x
This is a major release with breaking changes. See CHANGELOG.md for detailed migration guidance.
License
MIT License. Copyright (c) 2020–2025 Adi Eyal.
Release files for dictutils 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dictutils-1.0.1.tar.gz | 47.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dictutils-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.4 kB
Release files / dictutils-1.0.1.tar.gz
| Download URL | dictutils-1.0.1.tar.gz |
|---|---|
| Size | 47.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
58f11ddaba13a9951de60e19d46e28cecf0b23127192e93514ff84e810303166
|
|
BLAKE2b-256 checksum How to use checksums |
b7ace8874d3bf34c3691f185e23989d49e693e0cc02e711c825a03ed211e2250
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|
Release files / dictutils-1.0.1-py3-none-any.whl
| Download URL | dictutils-1.0.1-py3-none-any.whl |
|---|---|
| Size | 21.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
751f410ccf204ec18a36226b48c8efcda4bcd2cb2db3e0a584b874fd9ac0d2f4
|
|
BLAKE2b-256 checksum How to use checksums |
a860c6c384e50619953cf162982c0ae933378f674750638e405bd20f01b4575f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.12
|