end = composite(Point, x2, y2,
comparator_factory=PointComparator)
+Nesting Composites
+-------------------
+
+Composite objects can be defined to work in simple nested schemes, by
+redefining behaviors within the composite class to work as desired, then
+mapping the composite class to the full length of individual columns normally.
+Typically, it is convenient to define separate constructors for user-defined
+use and generate-from-row use. Below we reorganize the ``Vertex`` class to
+itself be a composite object, which is then mapped to a class ``HasVertex``::
+
+ from sqlalchemy.orm import composite
+
+ class Point(object):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ def __composite_values__(self):
+ return self.x, self.y
+
+ def __repr__(self):
+ return "Point(x=%r, y=%r)" % (self.x, self.y)
+
+ def __eq__(self, other):
+ return isinstance(other, Point) and \
+ other.x == self.x and \
+ other.y == self.y
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ class Vertex(object):
+ def __init__(self, start, end):
+ self.start = start
+ self.end = end
+
+ @classmethod
+ def _generate(self, x1, y1, x2, y2):
+ """generate a Vertex from a row"""
+ return Vertex(
+ Point(x1, y1),
+ Point(x2, y2)
+ )
+
+ def __composite_values__(self):
+ return \
+ self.start.__composite_values__() + \
+ self.end.__composite_values__()
+
+ class HasVertex(Base):
+ __tablename__ = 'has_vertex'
+ id = Column(Integer, primary_key=True)
+ x1 = Column(Integer)
+ y1 = Column(Integer)
+ x2 = Column(Integer)
+ y2 = Column(Integer)
+
+ vertex = composite(Vertex._generate, x1, y1, x2, y2)
+
+We can then use the above mapping as::
+
+ hv = HasVertex(vertex=Vertex(Point(1, 2), Point(3, 4)))
+
+ s.add(hv)
+ s.commit()
+
+ hv = s.query(HasVertex).filter(
+ HasVertex.vertex == Vertex(Point(1, 2), Point(3, 4))).first()
+ print(hv.vertex.start)
+ print(hv.vertex.end)
\ No newline at end of file
eq_(e.start, None)
+class NestedTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
+ @classmethod
+ def define_tables(cls, metadata):
+ Table('stuff', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column("a", String(30)),
+ Column("b", String(30)),
+ Column("c", String(30)),
+ Column("d", String(30)))
+
+ def _fixture(self):
+ class AB(object):
+ def __init__(self, a, b, cd):
+ self.a = a
+ self.b = b
+ self.cd = cd
+
+ @classmethod
+ def generate(cls, a, b, c, d):
+ return AB(a, b, CD(c, d))
+
+ def __composite_values__(self):
+ return (self.a, self.b) + self.cd.__composite_values__()
+
+ def __eq__(self, other):
+ return isinstance(other, AB) and \
+ self.a == other.a and self.b == other.b and \
+ self.cd == other.cd
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ class CD(object):
+ def __init__(self, c, d):
+ self.c = c
+ self.d = d
+
+ def __composite_values__(self):
+ return (self.c, self.d)
+
+ def __eq__(self, other):
+ return isinstance(other, CD) and \
+ self.c == other.c and self.d == other.d
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ class Thing(object):
+ def __init__(self, ab):
+ self.ab = ab
+
+ stuff = self.tables.stuff
+ mapper(Thing, stuff, properties={
+ "ab": composite(
+ AB.generate, stuff.c.a, stuff.c.b, stuff.c.c, stuff.c.d)
+ })
+ return Thing, AB, CD
+
+ def test_round_trip(self):
+ Thing, AB, CD = self._fixture()
+
+ s = Session()
+
+ s.add(Thing(AB('a', 'b', CD('c', 'd'))))
+ s.commit()
+
+ s.close()
+
+ t1 = s.query(Thing).filter(
+ Thing.ab == AB('a', 'b', CD('c', 'd'))).one()
+ eq_(t1.ab, AB('a', 'b', CD('c', 'd')))
+
+
class PrimaryKeyTest(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):