Small utilities for nested dicts: merge, pivot, grouping/aggregation.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dictutils-1.0.1.tar.gz.
File metadata
- Download URL: dictutils-1.0.1.tar.gz
- Upload date:
- Size: 47.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58f11ddaba13a9951de60e19d46e28cecf0b23127192e93514ff84e810303166
|
|
| MD5 |
217e1bfd2fe3dbd545f9583c0b66b234
|
|
| BLAKE2b-256 |
b7ace8874d3bf34c3691f185e23989d49e693e0cc02e711c825a03ed211e2250
|
File details
Details for the file dictutils-1.0.1-py3-none-any.whl.
File metadata
- Download URL: dictutils-1.0.1-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
751f410ccf204ec18a36226b48c8efcda4bcd2cb2db3e0a584b874fd9ac0d2f4
|
|
| MD5 |
0bc9a18f90c724ebaef12edb86e3af57
|
|
| BLAKE2b-256 |
a860c6c384e50619953cf162982c0ae933378f674750638e405bd20f01b4575f
|