dotdict
✦₊⁺ Overview
dotdict is a dictionary with dot notation access and nested path support. It extends Python's built-in dict, so anything that works with a regular dict works here too.
It is the foundation of Message and can be used standalone wherever flexible, deeply-nested data structures are needed.
Example
1. Creating a dotdict
From a dict
From keyword arguments
Combined
Empty
2. Reading Values
Dot access
Example
Bracket access
Example
Nested path with get()
Use get(path, default=None) to traverse nested keys via a dot-separated string:
Example
get() never raises — it returns default when any key in the path is missing.
3. Writing Values
Dot assignment
Bracket assignment
Nested path with set()
Use set(path, value) to write deeply nested values. Intermediate keys are created automatically:
Example
4. Nested Paths
Both get() and set() accept dot-separated strings to traverse any depth:
Example
List index access
Use integer segments in the path to index into lists:
Example
set() also supports writing into existing list positions:
Example
5. Auto-wrapping
Any dict assigned to a dotdict — whether at creation, via set(), or via attribute assignment — is automatically converted to a dotdict, so dot access always works:
Example
Lists of dicts are also wrapped recursively:
Example
6. update()
update() extends dict.update with two extras:
1. Dotted keys are written as nested paths:
Example
2. Dict values are merged recursively when the key already holds a dotdict:
Example
Standard positional argument and keyword argument forms are both supported:
7. Serialization
to_dict()
Converts the dotdict (and all nested dotdict values) back to a plain Python dict:
Example
to_json()
Returns a JSON-encoded bytes object, powered by msgspec:
8. Immutability
Pass frozen=True to create a read-only dotdict. Any attempt to write raises AttributeError:
Example
Nested dicts inherit the frozen flag automatically:
Example
9. Hidden Keys
hidden_keys marks keys as invisible to enumeration and discovery. They won't appear in iteration, serialization, or string representations — but can always be accessed directly if you know they exist.
Example
import msgflux as mf
d = mf.dotdict(
{"api_key": "sk-secret", "username": "john"},
hidden_keys=["api_key"]
)
# Enumeration — api_key is invisible
print("api_key" in d) # False
print(list(d.keys())) # ["username"]
print(list(d.values())) # ["john"]
print(list(d.items())) # [("username", "john")]
for k in d:
print(k) # "username"
print(d.to_dict()) # {"username": "john"}
print(d.to_json()) # b'{"username":"john"}'
print(d) # {'username': 'john'}
# Direct access — api_key is reachable
print(d.api_key) # "sk-secret"
print(d["api_key"]) # "sk-secret"
print(d.get("api_key")) # "sk-secret"
print(d.get("api_key", "x")) # "sk-secret"
10. Extending dotdict
dotdict is designed to be subclassed. You can add default fields, metadata, or custom behavior by overriding __init__:
Example
Message follows this exact pattern — it extends dotdict with default AI workflow fields (content, images, context, etc.) and metadata (user_id, chat_id, execution_id).