Permalink
Browse files

_AssociationList, _AssociationDict asdict support

  • Loading branch information...
danielholmstrom committed Jun 12, 2014
1 parent bd655a7 commit bc16d858673580a43221addc8560be4ed3bce361
Showing with 156 additions and 10 deletions.
  1. +5 −0 CHANGES.rst
  2. +75 −0 dictalchemy/tests/__init__.py
  3. +53 −1 dictalchemy/tests/test_asdict.py
  4. +21 −7 dictalchemy/utils.py
  5. +2 −2 docs/source/index.rst
View
@@ -1,3 +1,8 @@
+Version 0.1.2.5
+===============
+
+* Support for _AssociationList and _AssociationDict with asdict
+
Version 0.1.2.4
===============
@@ -12,6 +12,7 @@
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.orderinglist import ordering_list
+from sqlalchemy.ext.associationproxy import association_proxy
# Setup sqlalchemy
@@ -400,3 +401,77 @@ class OrderingParent(Base):
children = relationship(OrderingChild,
order_by=OrderingChild.position,
collection_class=ordering_list('position'))
+
+
+m2m_associationproxy_table = Table(
+ 'm2maassociationlisttable',
+ Base.metadata,
+ Column('parent_id', Integer, ForeignKey("m2massociationlistparent.id"),
+ primary_key=True),
+ Column('child_id', Integer, ForeignKey("m2massociationlistchild.id"),
+ primary_key=True))
+
+
+class M2MAssociationListParent(Base):
+
+ __tablename__ = 'm2massociationlistparent'
+
+ id = Column(Integer, primary_key=True)
+
+ children = relationship('M2MAssociationListChild',
+ secondary=lambda: m2m_associationproxy_table)
+
+ child_list = association_proxy('children', 'id')
+
+
+class M2MAssociationListChild(Base):
+
+ __tablename__ = 'm2massociationlistchild'
+
+ id = Column(Integer, primary_key=True)
+
+
+class M2MAssociationDictParent(Base):
+
+ __tablename__ = 'm2massociationdictparent'
+
+ id = Column(Integer, primary_key=True)
+
+ children = association_proxy(
+ 'child_ids',
+ 'id',
+ creator=lambda k, v: M2MAssociationDictKey(the_child_key=k,
+ the_child_value=v))
+
+
+class M2MAssociationDictKey(Base):
+
+ __tablename__ = 'm2massociationdictkey'
+
+ parent_id = Column(Integer,
+ ForeignKey('m2massociationdictparent.id'),
+ primary_key=True)
+
+ child_id = Column(Integer,
+ ForeignKey('m2massociationdictchild.id'),
+ primary_key=True)
+
+ the_child_key = Column(String)
+
+ user = relationship(M2MAssociationDictParent, backref=backref(
+ "children",
+ collection_class=attribute_mapped_collection("the_child_key"),
+ cascade="all, delete-orphan"))
+
+ _child_value = relationship("M2MAssociationDictChild")
+
+ child_value = association_proxy('_child_value', 'the_child_value')
+
+
+class M2MAssociationDictChild(Base):
+
+ __tablename__ = 'm2massociationdictchild'
+
+ id = Column(Integer, primary_key=True)
+
+ the_child_value = Column(String())
@@ -27,7 +27,9 @@
WithMethodWithExtraArgumentChild,
OrderingParent,
OrderingChild,
-)
+ M2MAssociationListParent,
+ M2MAssociationListChild,
+ M2MAssociationDictParent)
class TestAsdict(TestCase):
@@ -387,3 +389,53 @@ def test_orderinglist(self):
follow={'children': {'only': ['position']}}) == {
'children': [{'position': 0}, {'position': 1}],
}
+
+ def test_follow_m2m_association_list(self):
+
+ parent = M2MAssociationListParent()
+ child1 = M2MAssociationListChild(id=2)
+ child2 = M2MAssociationListChild(id=7)
+
+ parent.children.append(child1)
+ parent.children.append(child2)
+
+ assert parent.asdict(
+ only=['child_list'],
+ follow={'child_list': {}}) == {'child_list': [2, 7]}
+
+ self.session.add(parent)
+ self.session.add(child1)
+ self.session.add(child2)
+
+ self.session.commit()
+
+ assert parent.asdict(
+ only=['child_list'],
+ follow={'child_list': {}}) == {'child_list': [2, 7]}
+
+ def test_follow_m2m_association_dict(self):
+
+ parent = M2MAssociationDictParent()
+
+ parent.child_dict = {'key a': 'value a', 'key b': 'value b'}
+
+ assert parent.asdict(
+ only=['child_dict'],
+ follow={'child_dict': {}}) == {
+ 'child_dict': {
+ 'key a': 'value a',
+ 'key b': 'value b',
+ },
+ }
+
+ self.session.add(parent)
+ self.session.commit()
+
+ assert parent.asdict(
+ only=['child_dict'],
+ follow={'child_dict': {}}) == {
+ 'child_dict': {
+ 'key a': 'value a',
+ 'key b': 'value b',
+ },
+ }
View
@@ -8,6 +8,7 @@
import copy
from sqlalchemy import inspect
+from sqlalchemy.ext.associationproxy import _AssociationList
from sqlalchemy.orm.dynamic import AppenderMixin
from sqlalchemy.orm.query import Query
@@ -90,7 +91,6 @@ def asdict(model, exclude=None, exclude_underscore=None, exclude_pk=None,
columns = [c.key for c in info.mapper.column_attrs]
synonyms = [c.key for c in info.mapper.synonyms]
- relations = [c.key for c in info.mapper.relationships]
if only:
attrs = only
@@ -118,39 +118,53 @@ def asdict(model, exclude=None, exclude_underscore=None, exclude_pk=None,
data = dict([(k, getattr(model, k)) for k in attrs])
for (rel_key, orig_args) in follow.iteritems():
- if rel_key not in relations:
+
+ try:
+ rel = getattr(model, rel_key)
+ except AttributeError:
raise errors.MissingRelationError(rel_key)
args = copy.deepcopy(orig_args)
method = args.pop('method', method)
args['method'] = method
args.update(copy.copy(kwargs))
- rel = getattr(model, rel_key)
-
if hasattr(rel, method):
rel_data = getattr(rel, method)(**args)
- elif isinstance(rel, list):
+ elif isinstance(rel, (list, _AssociationList)):
rel_data = []
+
for child in rel:
if hasattr(child, method):
rel_data.append(getattr(child, method)(**args))
else:
- rel_data.append(dict(child))
+ try:
+ rel_data.append(dict(child))
+ # TypeError is for non-dictable children
+ except TypeError:
+ rel_data.append(copy.copy(child))
+
elif isinstance(rel, dict):
rel_data = {}
+
for (child_key, child) in rel.iteritems():
if hasattr(child, method):
rel_data[child_key] = getattr(child, method)(**args)
else:
- rel_data[child_key] = child.dict(child)
+ try:
+ rel_data[child_key] = dict(child)
+ except ValueError:
+ rel_data[child_key] = copy.copy(child)
+
elif isinstance(rel, (AppenderMixin, Query)):
rel_data = []
+
for child in rel.all():
if hasattr(child, method):
rel_data.append(getattr(child, method)(**args))
else:
rel_data.append(dict(child))
+
elif rel is None:
rel_data = None
else:
View
@@ -7,8 +7,6 @@ Dictalchemy adds :func:`utils.asdict` and :func:`utils.fromdict` methods to `SQL
Currently this works with synonyms and simple relationships as one-to-many and many-to-many. Relationships can be followed in many levels.
-Any collection that inherits from `dict` or `list` is supported together with :class:`sqlalchemy.orm.dynamic.AppenderMixin` and :class:`sqlalchemy.orm.query.Query`.
-
Using DictableModel
-------------------
@@ -38,6 +36,8 @@ Default values are defined in :mod:`dictalchemy.constants`.
Using asdict()
--------------
+Any collection that inherits from `dict` or `list` is supported together with :class:`sqlalchemy.orm.dynamic.AppenderMixin`, :class:`sqlalchemy.orm.query.Query` :class:`sqlalchemy.orm.associationproxy._AssociationList` and :class:`sqlalchemy.orm.associationproxy._AssociationDict`.
+
Simple example::
>>> session.query(User).asdict()

0 comments on commit bc16d85

Please sign in to comment.