13
from sqlalchemy.orm.interfaces import \
14
MapperProperty, PropComparator, StrategizedProperty
15
from sqlalchemy.orm.mapper import _none_set
16
from sqlalchemy.orm import attributes, strategies
17
from sqlalchemy import util, sql, exc as sa_exc, event, schema
18
from sqlalchemy.sql import expression
13
from .interfaces import MapperProperty, PropComparator
14
from .util import _none_set
15
from . import attributes, strategies
16
from .. import util, sql, exc as sa_exc, event, schema
17
from ..sql import expression
19
18
properties = util.importlater('sqlalchemy.orm', 'properties')
21
21
class DescriptorProperty(MapperProperty):
22
22
""":class:`.MapperProperty` which proxies access to a
23
23
user-defined descriptor."""
67
70
lambda: self._comparator_factory(mapper),
72
original_property=self
70
74
proxy_attr.impl = _ProxyImpl(self.key)
71
75
mapper.class_manager.instrument_attribute(self.key, proxy_attr)
74
78
class CompositeProperty(DescriptorProperty):
79
"""Defines a "composite" mapped attribute, representing a collection
80
of columns as one attribute.
82
:class:`.CompositeProperty` is constructed using the :func:`.composite`
87
:ref:`mapper_composite`
76
90
def __init__(self, class_, *attrs, **kwargs):
78
92
self.composite_class = class_
81
95
self.group = kwargs.get('group', None)
82
96
self.comparator_factory = kwargs.pop('comparator_factory',
83
97
self.__class__.Comparator)
99
self.info = kwargs.pop('info')
84
101
util.set_creation_order(self)
85
102
self._create_descriptor()
110
127
# key not present. Iterate through related
111
128
# attributes, retrieve their values. This
112
129
# ensures they all load.
113
values = [getattr(instance, key) for key in self._attribute_keys]
131
getattr(instance, key)
132
for key in self._attribute_keys
115
135
# current expected behavior here is that the composite is
116
136
# created on access if the object is persistent or if
225
245
state.dict.pop(self.key, None)
227
247
event.listen(self.parent, 'after_insert',
228
insert_update_handler, raw=True)
248
insert_update_handler, raw=True)
229
249
event.listen(self.parent, 'after_update',
230
insert_update_handler, raw=True)
231
event.listen(self.parent, 'load', load_handler, raw=True, propagate=True)
232
event.listen(self.parent, 'refresh', load_handler, raw=True, propagate=True)
233
event.listen(self.parent, "expire", expire_handler, raw=True, propagate=True)
250
insert_update_handler, raw=True)
251
event.listen(self.parent, 'load',
252
load_handler, raw=True, propagate=True)
253
event.listen(self.parent, 'refresh',
254
load_handler, raw=True, propagate=True)
255
event.listen(self.parent, 'expire',
256
expire_handler, raw=True, propagate=True)
235
258
# TODO: need a deserialize hook here
273
296
return attributes.History(
274
(),[self.composite_class(*added)], ()
297
(), [self.composite_class(*added)], ()
277
300
def _comparator_factory(self, mapper):
278
return self.comparator_factory(self)
301
return self.comparator_factory(self, mapper)
280
303
class Comparator(PropComparator):
281
def __init__(self, prop, adapter=None):
282
self.prop = self.property = prop
283
self.adapter = adapter
304
"""Produce boolean, comparison, and other operators for
305
:class:`.CompositeProperty` attributes.
307
See the example in :ref:`composite_operations` for an overview
308
of usage , as well as the documentation for :class:`.PropComparator`.
312
:class:`.PropComparator`
314
:class:`.ColumnOperators`
316
:ref:`types_operators`
318
:attr:`.TypeEngine.comparator_factory`
285
322
def __clause_element__(self):
287
# TODO: test coverage for adapted composite comparison
288
return expression.ClauseList(
289
*[self.adapter(x) for x in self.prop._comparable_elements])
291
return expression.ClauseList(*self.prop._comparable_elements)
323
return expression.ClauseList(group=False, *self._comparable_elements)
327
@util.memoized_property
328
def _comparable_elements(self):
330
# we need to do a little fudging here because
331
# the adapter function we're given only accepts
332
# ColumnElements, but our prop._comparable_elements is returning
333
# InstrumentedAttribute, because we support the use case
334
# of composites that refer to relationships. The better
335
# solution here is to open up how AliasedClass interacts
336
# with PropComparators so more context is available.
337
return [self.adapter(x.__clause_element__())
338
for x in self.prop._comparable_elements]
340
return self.prop._comparable_elements
295
342
def __eq__(self, other):
296
343
if other is None:
297
344
values = [None] * len(self.prop._comparable_elements)
299
346
values = other.__composite_values__()
301
*[a==b for a, b in zip(self.prop._comparable_elements, values)])
349
for a, b in zip(self.prop._comparable_elements, values)
352
comparisons = [self.adapter(x) for x in comparisons]
353
return sql.and_(*comparisons)
303
355
def __ne__(self, other):
304
356
return sql.not_(self.__eq__(other))