Module

utils.dotdict

DotDict - Dictionary with dot notation access.

Provides clean attribute-style access to dictionary data while avoiding Jinja2 template gotchas (like .items, .keys, .values accessing methods).

Classes

DotDict
Dictionary wrapper that allows dot notation access without method name conflicts. This class solve…
17

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.
2 Any
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.
0 KeysView[str]
def keys(self) -> KeysView[str]

Return dict keys.

Returns

KeysView[str]

values
Return dict values.
0 ValuesView[Any]
def values(self) -> ValuesView[Any]

Return dict values.

Returns

ValuesView[Any]

items
Return dict items - note this is the METHOD, not a field.
0 ItemsView[str, Any]
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.
1 DotDict
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

DotDict with all nested dicts also wrapped

to_dict
Convert back to regular dict.
0 dict[str, Any]
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.
1 None
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…
1 Any
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

Any

The data value if it exists, the attribute, or None

__setattr__
Allow dot notation assignment. Invalidates cache for the key.
2 None
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.
1 None
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.
1 Any
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.
2 None
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.
1 None
def __delitem__(self, key: str) -> None

Bracket notation deletion. Invalidates cache for the key.

Parameters 1
key str
__contains__
Check if key exists.
1 bool
def __contains__(self, key: str) -> bool

Check if key exists.

Parameters 1
key str
Returns

bool

__len__
Return number of keys.
0 int
def __len__(self) -> int

Return number of keys.

Returns

int

__iter__
Iterate over keys.
0 Iterator[str]
def __iter__(self) -> Iterator[str]

Iterate over keys.

Returns

Iterator[str]

__repr__
Custom repr showing it's a DotDict.
0 str
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…
1 Any
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

Any

Wrapped data with DotDict for all dicts