Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/orm/properties.py : 67%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/properties.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
8"""MapperProperty implementations.
10This is a private module which defines the behavior of individual ORM-
11mapped attributes.
13"""
14from __future__ import absolute_import
16from . import attributes
17from .interfaces import PropComparator
18from .interfaces import StrategizedProperty
19from .util import _orm_full_deannotate
20from .. import log
21from .. import util
22from ..sql import expression
25__all__ = ["ColumnProperty"]
28@log.class_logger
29class ColumnProperty(StrategizedProperty):
30 """Describes an object attribute that corresponds to a table column.
32 Public constructor is the :func:`_orm.column_property` function.
34 """
36 strategy_wildcard_key = "column"
38 __slots__ = (
39 "_orig_columns",
40 "columns",
41 "group",
42 "deferred",
43 "instrument",
44 "comparator_factory",
45 "descriptor",
46 "extension",
47 "active_history",
48 "expire_on_flush",
49 "info",
50 "doc",
51 "strategy_key",
52 "_creation_order",
53 "_is_polymorphic_discriminator",
54 "_mapped_by_synonym",
55 "_deferred_column_loader",
56 )
58 @util.deprecated_params(
59 extension=(
60 "0.7",
61 ":class:`.AttributeExtension` is deprecated in favor of the "
62 ":class:`.AttributeEvents` listener interface. The "
63 ":paramref:`.column_property.extension` parameter will be "
64 "removed in a future release.",
65 )
66 )
67 def __init__(self, *columns, **kwargs):
68 r"""Provide a column-level property for use with a mapping.
70 Column-based properties can normally be applied to the mapper's
71 ``properties`` dictionary using the :class:`_schema.Column`
72 element directly.
73 Use this function when the given column is not directly present within
74 the mapper's selectable; examples include SQL expressions, functions,
75 and scalar SELECT queries.
77 The :func:`_orm.column_property` function returns an instance of
78 :class:`.ColumnProperty`.
80 Columns that aren't present in the mapper's selectable won't be
81 persisted by the mapper and are effectively "read-only" attributes.
83 :param \*cols:
84 list of Column objects to be mapped.
86 :param active_history=False:
87 When ``True``, indicates that the "previous" value for a
88 scalar attribute should be loaded when replaced, if not
89 already loaded. Normally, history tracking logic for
90 simple non-primary-key scalar values only needs to be
91 aware of the "new" value in order to perform a flush. This
92 flag is available for applications that make use of
93 :func:`.attributes.get_history` or :meth:`.Session.is_modified`
94 which also need to know
95 the "previous" value of the attribute.
97 :param comparator_factory: a class which extends
98 :class:`.ColumnProperty.Comparator` which provides custom SQL
99 clause generation for comparison operations.
101 :param group:
102 a group name for this property when marked as deferred.
104 :param deferred:
105 when True, the column property is "deferred", meaning that
106 it does not load immediately, and is instead loaded when the
107 attribute is first accessed on an instance. See also
108 :func:`~sqlalchemy.orm.deferred`.
110 :param doc:
111 optional string that will be applied as the doc on the
112 class-bound descriptor.
114 :param expire_on_flush=True:
115 Disable expiry on flush. A column_property() which refers
116 to a SQL expression (and not a single table-bound column)
117 is considered to be a "read only" property; populating it
118 has no effect on the state of data, and it can only return
119 database state. For this reason a column_property()'s value
120 is expired whenever the parent object is involved in a
121 flush, that is, has any kind of "dirty" state within a flush.
122 Setting this parameter to ``False`` will have the effect of
123 leaving any existing value present after the flush proceeds.
124 Note however that the :class:`.Session` with default expiration
125 settings still expires
126 all attributes after a :meth:`.Session.commit` call, however.
128 :param info: Optional data dictionary which will be populated into the
129 :attr:`.MapperProperty.info` attribute of this object.
131 :param extension:
132 an :class:`.AttributeExtension` instance, or list of extensions,
133 which will be prepended to the list of attribute listeners for the
134 resulting descriptor placed on the class.
136 .. seealso::
138 :ref:`column_property_options` - to map columns while including
139 mapping options
141 :ref:`mapper_column_property_sql_expressions` - to map SQL
142 expressions
144 """
145 super(ColumnProperty, self).__init__()
146 self._orig_columns = [expression._labeled(c) for c in columns]
147 self.columns = [
148 expression._labeled(_orm_full_deannotate(c)) for c in columns
149 ]
150 self.group = kwargs.pop("group", None)
151 self.deferred = kwargs.pop("deferred", False)
152 self.instrument = kwargs.pop("_instrument", True)
153 self.comparator_factory = kwargs.pop(
154 "comparator_factory", self.__class__.Comparator
155 )
156 self.descriptor = kwargs.pop("descriptor", None)
157 self.extension = kwargs.pop("extension", None)
158 self.active_history = kwargs.pop("active_history", False)
159 self.expire_on_flush = kwargs.pop("expire_on_flush", True)
161 if "info" in kwargs:
162 self.info = kwargs.pop("info")
164 if "doc" in kwargs:
165 self.doc = kwargs.pop("doc")
166 else:
167 for col in reversed(self.columns):
168 doc = getattr(col, "doc", None)
169 if doc is not None:
170 self.doc = doc
171 break
172 else:
173 self.doc = None
175 if kwargs:
176 raise TypeError(
177 "%s received unexpected keyword argument(s): %s"
178 % (self.__class__.__name__, ", ".join(sorted(kwargs.keys())))
179 )
181 util.set_creation_order(self)
183 self.strategy_key = (
184 ("deferred", self.deferred),
185 ("instrument", self.instrument),
186 )
188 @util.dependencies("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
189 def _memoized_attr__deferred_column_loader(self, state, strategies):
190 return state.InstanceState._instance_level_callable_processor(
191 self.parent.class_manager,
192 strategies.LoadDeferredColumns(self.key),
193 self.key,
194 )
196 def __clause_element__(self):
197 """Allow the ColumnProperty to work in expression before it is turned
198 into an instrumented attribute.
199 """
201 return self.expression
203 @property
204 def expression(self):
205 """Return the primary column or expression for this ColumnProperty.
207 E.g.::
210 class File(Base):
211 # ...
213 name = Column(String(64))
214 extension = Column(String(8))
215 filename = column_property(name + '.' + extension)
216 path = column_property('C:/' + filename.expression)
218 .. seealso::
220 :ref:`mapper_column_property_sql_expressions_composed`
222 """
223 return self.columns[0]
225 def instrument_class(self, mapper):
226 if not self.instrument:
227 return
229 attributes.register_descriptor(
230 mapper.class_,
231 self.key,
232 comparator=self.comparator_factory(self, mapper),
233 parententity=mapper,
234 doc=self.doc,
235 )
237 def do_init(self):
238 super(ColumnProperty, self).do_init()
239 if len(self.columns) > 1 and set(self.parent.primary_key).issuperset(
240 self.columns
241 ):
242 util.warn(
243 (
244 "On mapper %s, primary key column '%s' is being combined "
245 "with distinct primary key column '%s' in attribute '%s'. "
246 "Use explicit properties to give each column its own "
247 "mapped attribute name."
248 )
249 % (self.parent, self.columns[1], self.columns[0], self.key)
250 )
252 def copy(self):
253 return ColumnProperty(
254 deferred=self.deferred,
255 group=self.group,
256 active_history=self.active_history,
257 *self.columns
258 )
260 def _getcommitted(
261 self, state, dict_, column, passive=attributes.PASSIVE_OFF
262 ):
263 return state.get_impl(self.key).get_committed_value(
264 state, dict_, passive=passive
265 )
267 def merge(
268 self,
269 session,
270 source_state,
271 source_dict,
272 dest_state,
273 dest_dict,
274 load,
275 _recursive,
276 _resolve_conflict_map,
277 ):
278 if not self.instrument:
279 return
280 elif self.key in source_dict:
281 value = source_dict[self.key]
283 if not load:
284 dest_dict[self.key] = value
285 else:
286 impl = dest_state.get_impl(self.key)
287 impl.set(dest_state, dest_dict, value, None)
288 elif dest_state.has_identity and self.key not in dest_dict:
289 dest_state._expire_attributes(
290 dest_dict, [self.key], no_loader=True
291 )
293 class Comparator(util.MemoizedSlots, PropComparator):
294 """Produce boolean, comparison, and other operators for
295 :class:`.ColumnProperty` attributes.
297 See the documentation for :class:`.PropComparator` for a brief
298 overview.
300 .. seealso::
302 :class:`.PropComparator`
304 :class:`.ColumnOperators`
306 :ref:`types_operators`
308 :attr:`.TypeEngine.comparator_factory`
310 """
312 __slots__ = "__clause_element__", "info", "expressions"
314 def _memoized_method___clause_element__(self):
315 if self.adapter:
316 return self.adapter(self.prop.columns[0])
317 else:
318 # no adapter, so we aren't aliased
319 # assert self._parententity is self._parentmapper
320 return self.prop.columns[0]._annotate(
321 {
322 "parententity": self._parententity,
323 "parentmapper": self._parententity,
324 }
325 )
327 def _memoized_attr_info(self):
328 """The .info dictionary for this attribute."""
330 ce = self.__clause_element__()
331 try:
332 return ce.info
333 except AttributeError:
334 return self.prop.info
336 def _memoized_attr_expressions(self):
337 """The full sequence of columns referenced by this
338 attribute, adjusted for any aliasing in progress.
340 .. versionadded:: 1.3.17
342 """
343 if self.adapter:
344 return [self.adapter(col) for col in self.prop.columns]
345 else:
346 # no adapter, so we aren't aliased
347 # assert self._parententity is self._parentmapper
348 return [
349 col._annotate(
350 {
351 "parententity": self._parententity,
352 "parentmapper": self._parententity,
353 "orm_key": self.prop.key,
354 }
355 )
356 for col in self.prop.columns
357 ]
359 def _fallback_getattr(self, key):
360 """proxy attribute access down to the mapped column.
362 this allows user-defined comparison methods to be accessed.
363 """
364 return getattr(self.__clause_element__(), key)
366 def operate(self, op, *other, **kwargs):
367 return op(self.__clause_element__(), *other, **kwargs)
369 def reverse_operate(self, op, other, **kwargs):
370 col = self.__clause_element__()
371 return op(col._bind_param(op, other), col, **kwargs)
373 def __str__(self):
374 return str(self.parent.class_.__name__) + "." + self.key