python-benedict is a Python dict subclass with keypath access, multi-format I/O, and transformation utilities. pip install python-benedict. Import: from benedict import benedict. Create: b = benedict({"a": {"b": 1}}). Access: b["a.b"] → 1. b.get("a.b", default=0). Set: b["a.b"] = 2. Delete: del b["a.b"]. Custom separator: benedict(data, keypath_separator="/"). From JSON: b = benedict.from_json('{"key": 1}'). From YAML: benedict.from_yaml("key: value"). From TOML: benedict.from_toml(toml_str). From URL: benedict.from_url("https://api.example.com/data.json"). From base64: benedict.from_base64(encoded). From CSV: benedict.from_csv(csv_str). To JSON: b.to_json(indent=2). To YAML: b.to_yaml(). Merge: b.merge(other) — deep merge. Subset: b.subset(["a.b", "a.c"]). Rename: b.rename("old_key", "new_key"). Clean: b.clean() — remove None values. Filter: b.filter(keys=["a", "b"]). b.invert() — swap keys/values. b.flatten(separator="_"). b.unflatten(separator="_"). b.keypaths() — all keypaths. b.match("a.*") — wildcard search. Clone: b.clone(). Swap: b.swap("key1", "key2"). b.traverse(fn). Standardize: b.standardize() — lowercase+underscore keys. Claude Code generates benedict config handlers, API response parsers, and nested data transformation utilities.
CLAUDE.md for python-benedict
## python-benedict Stack
- Version: python-benedict >= 0.33 | pip install python-benedict
- Create: benedict(dict) | from_json/yaml/toml/url/base64/csv class methods
- Access: b["a.b.c"] keypath | b.get("a.b", default=val)
- Mutate: b["a.b"] = val | b.merge(other) | b.clean() | b.rename("k","v")
- Output: b.to_json() | b.to_yaml() | b.to_toml() | b.to_csv()
- Utils: b.subset(keys) | b.flatten("_") | b.unflatten("_") | b.keypaths()
python-benedict Dict Pipeline
# app/dict_utils.py — benedict keypath access, format I/O, merge, transform, config
from __future__ import annotations
import os
from pathlib import Path
from typing import Any, Callable
from benedict import benedict
# ─────────────────────────────────────────────────────────────────────────────
# 1. Creation helpers
# ─────────────────────────────────────────────────────────────────────────────
def from_dict(data: dict, separator: str = ".") -> benedict:
"""Wrap an existing dict as a benedict with the given keypath separator."""
return benedict(data, keypath_separator=separator)
def from_json(json_str: str) -> benedict:
"""Parse a JSON string into a benedict."""
return benedict.from_json(json_str)
def from_yaml(yaml_str: str) -> benedict:
"""Parse a YAML string into a benedict."""
return benedict.from_yaml(yaml_str)
def from_toml(toml_str: str) -> benedict:
"""Parse a TOML string into a benedict."""
return benedict.from_toml(toml_str)
def load_file(path: str | Path) -> benedict:
"""
Load a config or data file by extension.
Supports: .json, .yaml/.yml, .toml, .csv, .xml
"""
p = Path(path)
suffix = p.suffix.lower()
text = p.read_text(encoding="utf-8")
if suffix == ".json":
return benedict.from_json(text)
if suffix in (".yaml", ".yml"):
return benedict.from_yaml(text)
if suffix == ".toml":
return benedict.from_toml(text)
if suffix == ".xml":
return benedict.from_xml(text)
if suffix == ".csv":
return benedict.from_csv(text)
raise ValueError(f"Unsupported file extension: {suffix}")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Safe access helpers
# ─────────────────────────────────────────────────────────────────────────────
def get(b: benedict, path: str, default: Any = None) -> Any:
"""Access a keypath with a default. Alias for b.get(path, default)."""
return b.get(path, default)
def require(b: benedict, path: str) -> Any:
"""Access a keypath; raise KeyError if missing."""
val = b.get(path)
if val is None:
raise KeyError(f"Required key missing: {path!r}")
return val
def pluck(b: benedict, *paths: str, default: Any = None) -> dict[str, Any]:
"""Extract multiple keypaths into a flat dict."""
return {path: b.get(path, default) for path in paths}
def first_of(b: benedict, *paths: str, default: Any = None) -> Any:
"""Return the value of the first path that resolves to a non-None value."""
for path in paths:
val = b.get(path)
if val is not None:
return val
return default
# ─────────────────────────────────────────────────────────────────────────────
# 3. Merge and combine
# ─────────────────────────────────────────────────────────────────────────────
def deep_merge(*dicts: dict) -> benedict:
"""
Deep-merge multiple dicts. Later keys override earlier.
Example:
merged = deep_merge(defaults, overrides, env_values)
"""
result = benedict()
for d in dicts:
result.merge(d)
return result
def merge_with_defaults(data: dict, defaults: dict) -> benedict:
"""Apply defaults to data — data values take priority."""
b = benedict(defaults)
b.merge(data)
return b
# ─────────────────────────────────────────────────────────────────────────────
# 4. Transformation helpers
# ─────────────────────────────────────────────────────────────────────────────
def clean(b: benedict, strings: bool = True, dicts: bool = True, lists: bool = True) -> benedict:
"""
Remove None (and optionally empty string/dict/list) values.
Returns a cleaned clone (does not mutate b).
"""
clone = b.clone()
clone.clean(strings=strings, dicts=dicts, lists=lists)
return clone
def flatten_dict(b: benedict, separator: str = "_") -> benedict:
"""
Flatten nested keys into a single-level dict.
{"a": {"b": 1}} → {"a_b": 1}
"""
clone = b.clone()
clone.flatten(separator=separator)
return clone
def unflatten_dict(b: benedict, separator: str = "_") -> benedict:
"""
Unflatten a flat dict into a nested structure.
{"a_b": 1} → {"a": {"b": 1}}
"""
clone = b.clone()
clone.unflatten(separator=separator)
return clone
def subset(b: benedict, keys: list[str]) -> benedict:
"""
Extract a subset of keypaths into a new benedict.
Example:
subset(user, ["id", "profile.email", "settings.theme"])
"""
return b.subset(keys)
def rename_keys(b: benedict, mapping: dict[str, str]) -> benedict:
"""
Rename top-level keys according to a mapping dict.
Returns a clone with renamed keys.
"""
clone = b.clone()
for old, new in mapping.items():
if old in clone:
clone.rename(old, new)
return clone
def standardize(b: benedict) -> benedict:
"""
Standardize all keys: lowercase + underscore.
{"FirstName": "Alice"} → {"first_name": "Alice"}
"""
clone = b.clone()
clone.standardize()
return clone
def filter_keys(b: benedict, predicate: Callable[[str], bool]) -> benedict:
"""
Keep only top-level keys where predicate(key) is True.
"""
keys = [k for k in b.keys() if predicate(k)]
return b.subset(keys)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Config management
# ─────────────────────────────────────────────────────────────────────────────
def load_config(
base: dict | str | Path,
env_prefix: str = "",
overrides: dict | None = None,
) -> benedict:
"""
Build an application config from layered sources:
1. base dict or file path
2. environment variables with optional prefix
3. explicit overrides dict
Example:
cfg = load_config("config/base.yaml", env_prefix="APP_")
db_host = cfg["database.host"]
"""
if isinstance(base, (str, Path)):
b = load_file(base)
else:
b = benedict(base)
if env_prefix:
prefix = env_prefix.upper()
prefix_len = len(prefix)
env_layer: dict = {}
for key, val in os.environ.items():
if key.upper().startswith(prefix):
clean_key = key[prefix_len:].lower().replace("__", ".")
env_layer[clean_key] = val
if env_layer:
b.merge(env_layer)
if overrides:
b.merge(overrides)
return b
def save_config(b: benedict, path: str | Path) -> None:
"""Save a benedict config to a file (JSON, YAML, or TOML by extension)."""
p = Path(path)
suffix = p.suffix.lower()
if suffix == ".json":
p.write_text(b.to_json(indent=2), encoding="utf-8")
elif suffix in (".yaml", ".yml"):
p.write_text(b.to_yaml(), encoding="utf-8")
elif suffix == ".toml":
p.write_text(b.to_toml(), encoding="utf-8")
else:
raise ValueError(f"Unsupported extension: {suffix}")
# ─────────────────────────────────────────────────────────────────────────────
# 6. API response helpers
# ─────────────────────────────────────────────────────────────────────────────
def normalize_response(
response: dict,
schema: dict[str, str],
defaults: dict | None = None,
) -> benedict:
"""
Normalize an API response by remapping keypaths to canonical names.
schema: {canonical_name: source_keypath}
Example:
normalize_response(
resp,
{"user_id": "data.user.id", "email": "data.user.contact.email"},
)
"""
b = benedict(response)
result = benedict(defaults or {})
for canonical, source_path in schema.items():
val = b.get(source_path)
if val is not None:
result[canonical] = val
return result
def paginated_items(
response: dict,
items_path: str = "data",
total_path: str = "meta.total",
) -> tuple[list[Any], int]:
"""
Extract items and total count from a paginated API response.
Returns (items, total).
"""
b = benedict(response)
items = b.get(items_path, [])
total = b.get(total_path, len(items))
return items, total
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== Keypath access ===")
data = {"user": {"profile": {"name": "Alice", "age": 30}, "roles": ["admin", "user"]}}
b = from_dict(data)
print("name: ", b["user.profile.name"])
print("age: ", b.get("user.profile.age"))
print("city: ", b.get("user.profile.city", "N/A"))
print("role0: ", b.get("user.roles.0"))
print("\n=== Assign via keypath ===")
b["user.profile.city"] = "New York"
print("city now:", b["user.profile.city"])
print("\n=== Pluck ===")
extracted = pluck(b, "user.profile.name", "user.profile.age", "user.roles")
print(extracted)
print("\n=== Deep merge ===")
defaults = benedict({"debug": False, "database": {"host": "localhost", "port": 5432}})
overrides = {"database": {"port": 5433}, "debug": True}
merged = deep_merge(defaults, overrides)
print("debug:", merged["debug"], "port:", merged["database.port"])
print("\n=== Clean ===")
messy = from_dict({"a": 1, "b": None, "c": "", "d": {"x": None, "y": 2}})
cleaned = clean(messy)
print("cleaned:", dict(cleaned))
print("\n=== Flatten / Unflatten ===")
nested = from_dict({"db": {"host": "localhost", "port": 5432}, "app": {"debug": True}})
flat = flatten_dict(nested)
print("flat:", dict(flat))
restored = unflatten_dict(flat)
print("restored db.host:", restored["db.host"])
print("\n=== Rename keys ===")
raw = from_dict({"firstName": "Bob", "lastName": "Smith", "emailAddress": "[email protected]"})
renamed = rename_keys(raw, {"firstName": "first_name", "lastName": "last_name"})
print("keys:", list(renamed.keys()))
print("\n=== Standardize ===")
camel = from_dict({"FirstName": "Carol", "LastName": "White", "IsActive": True})
std = standardize(camel)
print("keys:", list(std.keys()))
print("\n=== from_json / to_yaml ===")
j = '{"server": {"host": "0.0.0.0", "port": 8080}, "debug": true}'
cfg = from_json(j)
print("host:", cfg["server.host"])
print("yaml:\n" + cfg.to_yaml())
print("\n=== Keypaths ===")
small = from_dict({"a": {"b": 1, "c": {"d": 2}}, "e": 3})
print("all keypaths:", small.keypaths())
For the addict alternative — addict provides attribute-style access to dict keys (obj.a.b) but doesn’t support keypaths as strings, format I/O, or transformation utilities; benedict gives you b["a.b"] string keypath access, from_json/yaml/toml/url parsing, subset/flatten/merge/standardize all in one. For the munch alternative — munch converts dicts to objects with attribute access (m.a.b) and supports serialization; benedict is better when you need keypath string traversal, multi-format file loading, config layering from environment variables, and transformation pipelines without converting to a custom object type. The Claude Skills 360 bundle includes python-benedict skill sets covering from_dict/json/yaml/toml/file creation, get/require/pluck/first_of access helpers, deep_merge/merge_with_defaults, clean/flatten/unflatten/subset/rename_keys/standardize/filter_keys transformation, load_config() with env prefix layering, save_config() multi-format output, normalize_response() API response mapping, and paginated_items() extractor. Start with the free tier to try dict keypath navigation code generation.