import datetime
import uuid
from typing import (
Any,
Callable,
Dict,
Optional,
)
[docs]def dict_for(obj, **kwds):
# Create dict to represent item.
return dict(model_class=obj.__class__.__name__, **kwds)
[docs]class Dictifiable:
"""Mixin that enables objects to be converted to dictionaries. This is useful
when for sharing objects across boundaries, such as the API, tool scripts,
and JavaScript code."""
[docs] def to_dict(self, view: str = "collection", value_mapper: Optional[Dict[str, Callable]] = None) -> Dict[str, Any]:
"""
Return item dictionary.
"""
if not value_mapper:
value_mapper = {}
def get_value(key, item):
"""
Recursive helper function to get item values.
"""
# FIXME: why use exception here? Why not look for key in value_mapper
# first and then default to to_dict?
try:
return item.to_dict(view=view, value_mapper=value_mapper)
except Exception:
assert value_mapper is not None
if key in value_mapper:
return value_mapper[key](item)
if isinstance(item, datetime.datetime):
return item.isoformat()
elif isinstance(item, uuid.UUID):
return str(item)
# Leaving this for future reference, though we may want a more
# generic way to handle special type mappings going forward.
# If the item is of a class that needs to be 'stringified' before being put into a JSON data structure
# elif type(item) in []:
# return str(item)
return item
# Create dict to represent item.
rval = dict_for(self)
# Fill item dict with visible keys.
try:
visible_keys = self.__getattribute__(f"dict_{view}_visible_keys")
except AttributeError:
raise Exception(f"Unknown Dictifiable view: {view}")
for key in visible_keys:
try:
item = self.__getattribute__(key)
if isinstance(item, list):
rval[key] = []
for i in item:
rval[key].append(get_value(key, i))
else:
rval[key] = get_value(key, item)
except AttributeError:
rval[key] = None
return rval