"""DictizableMixin
A mixin class to add `todict` method to objects.
"""
from sqlalchemy.ext.associationproxy import AssociationProxy
from .utils import is_list_like, is_dict_like
from toolspy import deep_group
import json
from .json_encoder import json_encoder
[docs]def serialized_list(olist, rels_to_expand=[]):
return map(
lambda o: o.todict(
rels_to_expand=rels_to_expand),
olist)
[docs]class DictizableMixin(object):
"""
Attributes
----------
_attrs_to_serialize_ : list of str
The columns which should be serialized as a part of the output
dictionary
_key_modifications_ : dict of str,str
A dictionary used to map the display names of columns whose
original name you want to be modified in the json
_rels_to_serialize_ : list of (str, str)
A list of tuples. The first element of the tuple is the relationship
that is to be serialized. The second element it the name of the
attribute in the related model, the value of which is to be used
as the representation
_rels_to_expand_ : list of str
A list of relationships to expand. You can specify nested relationships
by placing dots.
_group_listrels_by_ : dict of str, list of str
A dictionary representing how to hierarchially group a list
like relationship. The relationship fields are the keys and
the list of the attributes based on which they are to be grouped
are the values.
"""
_attrs_to_serialize_ = []
_key_modifications_ = {}
_rels_to_serialize_ = []
_rels_to_expand_ = []
_group_listrels_by_ = {}
@classmethod
[docs] def is_list_attribute(cls, rel):
if rel in cls.__mapper__.relationships:
return cls.__mapper__.relationships[rel].uselist
rel_instance = getattr(cls, rel)
if isinstance(rel_instance, AssociationProxy):
return cls.__mapper__.relationships[
rel_instance.target_collection].uselist
return False
[docs] def to_serializable_dict(self, attrs_to_serialize=None,
rels_to_expand=None,
rels_to_serialize=None,
key_modifications=None):
"""
Just an alias for some functions which might still use old name
"""
return self.todict(
attrs_to_serialize=attrs_to_serialize,
rels_to_expand=rels_to_expand, rels_to_serialize=rels_to_serialize,
key_modifications=key_modifications)
# Version 5.0
[docs] def todict(self, attrs_to_serialize=None,
rels_to_expand=None,
rels_to_serialize=None,
group_listrels_by=None,
key_modifications=None):
"""Converts an instance to a dictionary form
Parameters
----------
attrs_to_serialize : list of str
The columns which should be serialized as a part of the output
dictionary
key_modifications : dict of str,str
A dictionary used to map the display names of columns whose
original name you want to be modified in the json
rels_to_serialize : list of (str, str)
A list of tuples. The first element of the tuple is the relationship
that is to be serialized. The second element it the name of the
attribute in the related model, the value of which is to be used
as the representation
rels_to_expand : list of str
A list of relationships to expand. You can specify nested relationships
by placing dots.
group_listrels_by : dict of str, list of str
A dictionary representing how to hierarchially group a list
like relationship. The relationship fields are the keys and
the list of the attributes based on which they are to be grouped
are the values.
"""
# Never replace the following code by the (attrs = attrs or
# self._attrs_) idiom. Python considers empty list as false. So
# even if you pass an empty list, it will take self._x_ value. But
# we don't want that as the empty list is what we use to end
# the recursion
attrs_to_serialize = (
self._attrs_to_serialize_ if attrs_to_serialize is None
else attrs_to_serialize)
rels_to_serialize = (
self._rels_to_serialize_ if rels_to_serialize is None
else rels_to_serialize)
rels_to_expand = (
self._rels_to_expand_ if rels_to_expand is None
else rels_to_expand)
key_modifications = (
self._key_modifications_ if key_modifications is None
else key_modifications)
group_listrels_by = (
self._group_listrels_by_ if group_listrels_by is None
else group_listrels_by)
# Convert rels_to_expand to a dictionary
rels_to_expand_dict = {}
for rel in rels_to_expand:
partitioned_rels = rel.partition('.')
if partitioned_rels[0] not in rels_to_expand_dict:
rels_to_expand_dict[partitioned_rels[0]] = (
[partitioned_rels[-1]] if partitioned_rels[-1]
else [])
else:
if partitioned_rels[-1]:
rels_to_expand_dict[partitioned_rels[0]].append(
partitioned_rels[-1])
# # Convert grouplistrelsby to a dict
# group_listrels_dict = {}
# for rel_to_group, grouping_keys in group_listrels_by.iteritems():
# partitioned_rel_to_group = rel_to_group.partition('.')
# if partitioned_rel_to_group[0] not in group_listrels_dict:
# group_listrels_dict[partitioned_rel_to_group[0]] = (
# {partitioned_rel_to_group[-1]: grouping_keys}
# if partitioned_rel_to_group[-1] else grouping_keys)
# else:
# if partitioned_rel_to_group[-1]:
# group_listrels_dict[
# partitioned_rel_to_group[0]][
# partitioned_rel_to_group[-1]] = grouping_keys
# Serialize attrs
result = self.serialize_attrs(*attrs_to_serialize)
# Serialize rels
if len(rels_to_serialize) > 0:
for rel, id_attr in rels_to_serialize:
rel_obj = getattr(self, rel, None)
if rel_obj is not None:
if is_list_like(rel_obj):
if (group_listrels_by is not None and
rel in group_listrels_by):
result[rel] = deep_group(
rel_obj,
attr_to_show=id_attr,
keys=group_listrels_by[rel]
)
else:
result[rel] = [getattr(item, id_attr)
for item in rel_obj]
elif is_dict_like(rel_obj):
result[rel] = {k: getattr(v, id_attr)
for k, v in rel_obj.iteritems()}
else:
result[rel] = getattr(rel_obj, id_attr)
# Expand some rels
for rel, child_rels in rels_to_expand_dict.iteritems():
rel_obj = getattr(self, rel, None)
if rel_obj is not None:
if is_list_like(rel_obj):
if (group_listrels_by is not None and
rel in group_listrels_by):
result[rel] = deep_group(
rel_obj,
keys=group_listrels_by[rel], serializer='todict',
serializer_kwargs={'rels_to_expand': child_rels}
)
else:
result[rel] = [i.todict(rels_to_expand=child_rels)
if hasattr(i, 'todict') else i
for i in rel_obj]
# result[rel] = serialized_list(
# rel_obj, rels_to_expand=child_rels)
elif is_dict_like(rel_obj):
result[rel] = {k: v.todict()
if hasattr(v, 'todict') else v
for k, v in rel_obj.iteritems()}
else:
result[rel] = rel_obj.todict(
rels_to_expand=child_rels) if hasattr(
rel_obj, 'todict') else rel_obj
for key, mod_key in key_modifications.items():
if key in result:
result[mod_key] = result.pop(key)
return result
[docs] def serialize_attrs(self, *args):
"""Converts and instance to a dictionary with only the specified
attributes as keys
Parameters
----------
*args : args list of str
The arguments to serialize
Examples
--------
>>> customer = Customer.create(name="James Bond", email="007@mi.com",
phone="007", city="London")
>>> customer.serialize_attrs('name', 'email')
{'name': u'James Bond', 'email': u'007@mi.com'}
"""
return dict([(a, getattr(self, a)) for a in args])
[docs] def tojson(self, attrs_to_serialize=None,
rels_to_expand=None,
rels_to_serialize=None,
key_modifications=None):
return json.dumps(
self.todict(
attrs_to_serialize=attrs_to_serialize,
rels_to_expand=rels_to_expand,
rels_to_serialize=rels_to_serialize,
key_modifications=key_modifications),
default=json_encoder)