mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-24 09:31:48 -04:00
Document and support nested composites
Composites can behave in a "nested" fashion by defining the
class in that way. To make the constructor more convenient,
a callable can be passed to :func:`.composite` instead of the
class itself. This works now, so add a test to ensure this
pattern remains available.
Change-Id: Ia009f274fca7269f41d6d824e0f70b6fb0ada081
(cherry picked from commit d4a130bb1b)
This commit is contained in:
Vendored
+70
@@ -148,3 +148,73 @@ the same expression that the base "greater than" does::
|
||||
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)
|
||||
@@ -100,7 +100,9 @@ class CompositeProperty(DescriptorProperty):
|
||||
is the :class:`.CompositeProperty`.
|
||||
|
||||
:param class\_:
|
||||
The "composite type" class.
|
||||
The "composite type" class, or any classmethod or callable which
|
||||
will produce a new instance of the composite object given the
|
||||
column values in order.
|
||||
|
||||
:param \*cols:
|
||||
List of Column objects to be mapped.
|
||||
|
||||
@@ -11,6 +11,8 @@ from sqlalchemy.testing import eq_
|
||||
from sqlalchemy.testing import fixtures
|
||||
|
||||
|
||||
|
||||
|
||||
class PointTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
|
||||
@classmethod
|
||||
def define_tables(cls, metadata):
|
||||
@@ -365,6 +367,79 @@ class PointTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user