Classes
DotDict
Dictionary wrapper that allows dot notation access without method name conflicts.
This class solve…
DotDict
Dictionary wrapper that allows dot notation access without method name conflicts.
This class solves a common Jinja2 gotcha: when using dot notation on a regular dict, Jinja2 will access dict methods (like .items()) instead of dictionary keys named "items".
Example Problem:
>>> data = {"skills": [{"category": "Programming", "items": ["Python"]}]}
>>> # In Jinja2 template:
>>> # {{ skill_group.items }} # Accesses .items() method! ❌
Solution with DotDict:
>>> data = DotDict({"skills": [{"category": "Programming", "items": ["Python"]}]})
>>> # In Jinja2 template:
>>> # {{ skill_group.items }} # Accesses "items" field! ✅
Features:
- Dot notation: obj.key
- Bracket notation: obj['key']
- Recursive wrapping of nested dicts (with caching for performance)
- Dict-like interface (but not inheriting from dict)
- No method name collisions
Usage:
>>> # Create from dict
>>> data = DotDict({"name": "Alice", "age": 30})
>>> data.name
'Alice'
>>> # Nested dicts auto-wrapped
>>> data = DotDict({"user": {"name": "Bob"}})
>>> data.user.name
'Bob'
>>> # Lists preserved
>>> data = DotDict({"items": ["a", "b", "c"]})
>>> data.items # Returns list, not a method!
['a', 'b', 'c']
Implementation Note:
Unlike traditional dict subclasses, DotDict does NOT inherit from dict.
This avoids method name collisions. We implement the dict interface
manually to work with Jinja2 and other dict-expecting code.
Performance:
Nested dictionaries are wrapped lazily and cached on first access.
This prevents repeatedly creating new DotDict objects for the same
nested data, which is especially important for deeply nested structures
or when accessed in template loops.
Methods 6
get
Get value with default.
get
def get(self, key: str, default: Any = None) -> Any
Get value with default.
Parameters 2
key |
str |
|
default |
Any |
Returns
Any
keys
Return dict keys.
keys
def keys(self) -> KeysView[str]
Return dict keys.
Returns
KeysView[str]
values
Return dict values.
values
def values(self) -> ValuesView[Any]
Return dict values.
Returns
ValuesView[Any]
items
Return dict items - note this is the METHOD, not a field.
items
def items(self) -> ItemsView[str, Any]
Return dict items - note this is the METHOD, not a field.
Returns
ItemsView[str, Any]
from_dict
classmethod
Create DotDict from a regular dict, recursively wrapping nested dicts.
from_dict
classmethod def from_dict(cls, data: dict[str, Any]) -> DotDict
Create DotDict from a regular dict, recursively wrapping nested dicts.
Parameters 1
data |
dict[str, Any] |
Source dictionary |
Returns
DotDict with all nested dicts also wrappedDotDict
—
to_dict
Convert back to regular dict.
to_dict
def to_dict(self) -> dict[str, Any]
Convert back to regular dict.
Returns
dict[str, Any]
Internal Methods 11
__init__
Initialize with a dictionary and a cache for wrapped nested objects.
__init__
def __init__(self, data: dict[str, Any] | None = None)
Initialize with a dictionary and a cache for wrapped nested objects.
Parameters 1
data |
dict[str, Any] | None |
__getattribute__
Intercept attribute access to prioritize data fields over methods.
This ensure…
__getattribute__
def __getattribute__(self, key: str) -> Any
Intercept attribute access to prioritize data fields over methods.
This ensures that if a data field has the same name as a method (like 'items', 'keys', 'values'), the data field is returned.
For Jinja2 compatibility, if a key doesn't exist in data and isn't
a real attribute, we return None instead of raising AttributeError.
This allows templates to safely checkif obj.fieldwithout errors.
Parameters 1
key |
str |
The attribute name |
Returns
The data value if it exists, the attribute, or NoneAny
—
__setattr__
Allow dot notation assignment. Invalidates cache for the key.
__setattr__
def __setattr__(self, key: str, value: Any) -> None
Allow dot notation assignment. Invalidates cache for the key.
Parameters 2
key |
str |
|
value |
Any |
__delattr__
Allow dot notation deletion. Invalidates cache for the key.
__delattr__
def __delattr__(self, key: str) -> None
Allow dot notation deletion. Invalidates cache for the key.
Parameters 1
key |
str |
__getitem__
Bracket notation access with caching.
__getitem__
def __getitem__(self, key: str) -> Any
Bracket notation access with caching.
Parameters 1
key |
str |
Returns
Any
__setitem__
Bracket notation assignment. Invalidates cache for the key.
__setitem__
def __setitem__(self, key: str, value: Any) -> None
Bracket notation assignment. Invalidates cache for the key.
Parameters 2
key |
str |
|
value |
Any |
__delitem__
Bracket notation deletion. Invalidates cache for the key.
__delitem__
def __delitem__(self, key: str) -> None
Bracket notation deletion. Invalidates cache for the key.
Parameters 1
key |
str |
__contains__
Check if key exists.
__contains__
def __contains__(self, key: str) -> bool
Check if key exists.
Parameters 1
key |
str |
Returns
bool
__len__
Return number of keys.
__len__
def __len__(self) -> int
Return number of keys.
Returns
int
__iter__
Iterate over keys.
__iter__
def __iter__(self) -> Iterator[str]
Iterate over keys.
Returns
Iterator[str]
__repr__
Custom repr showing it's a DotDict.
__repr__
def __repr__(self) -> str
Custom repr showing it's a DotDict.
Returns
str
Functions
wrap_data
Recursively wrap dictionaries in DotDict for clean access.
This is the main helper function for wr…
wrap_data
def wrap_data(data: Any) -> Any
Recursively wrap dictionaries in DotDict for clean access.
This is the main helper function for wrapping data loaded from YAML/JSON files before passing to templates.
Parameters 1
| Name | Type | Default | Description |
|---|---|---|---|
data |
Any |
— | Data to wrap (can be dict, list, or primitive) |
Returns
Wrapped data with DotDict for all dictsAny
—