Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/properties.py: 54%
123 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# orm/properties.py
2# Copyright (C) 2005-2023 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: https://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 .descriptor_props import CompositeProperty
18from .descriptor_props import ConcreteInheritedProperty
19from .descriptor_props import SynonymProperty
20from .interfaces import PropComparator
21from .interfaces import StrategizedProperty
22from .relationships import RelationshipProperty
23from .. import log
24from .. import util
25from ..sql import coercions
26from ..sql import roles
29__all__ = [
30 "ColumnProperty",
31 "CompositeProperty",
32 "ConcreteInheritedProperty",
33 "RelationshipProperty",
34 "SynonymProperty",
35]
38@log.class_logger
39class ColumnProperty(StrategizedProperty):
40 """Describes an object attribute that corresponds to a table column.
42 Public constructor is the :func:`_orm.column_property` function.
44 """
46 strategy_wildcard_key = "column"
47 inherit_cache = True
48 _links_to_entity = False
50 __slots__ = (
51 "columns",
52 "group",
53 "deferred",
54 "instrument",
55 "comparator_factory",
56 "descriptor",
57 "active_history",
58 "expire_on_flush",
59 "info",
60 "doc",
61 "strategy_key",
62 "_creation_order",
63 "_is_polymorphic_discriminator",
64 "_mapped_by_synonym",
65 "_deferred_column_loader",
66 "_raise_column_loader",
67 "_renders_in_subqueries",
68 "raiseload",
69 )
71 def __init__(self, *columns, **kwargs):
72 r"""Provide a column-level property for use with a mapping.
74 Column-based properties can normally be applied to the mapper's
75 ``properties`` dictionary using the :class:`_schema.Column`
76 element directly.
77 Use this function when the given column is not directly present within
78 the mapper's selectable; examples include SQL expressions, functions,
79 and scalar SELECT queries.
81 The :func:`_orm.column_property` function returns an instance of
82 :class:`.ColumnProperty`.
84 Columns that aren't present in the mapper's selectable won't be
85 persisted by the mapper and are effectively "read-only" attributes.
87 :param \*cols:
88 list of Column objects to be mapped.
90 :param active_history=False:
91 When ``True``, indicates that the "previous" value for a
92 scalar attribute should be loaded when replaced, if not
93 already loaded. Normally, history tracking logic for
94 simple non-primary-key scalar values only needs to be
95 aware of the "new" value in order to perform a flush. This
96 flag is available for applications that make use of
97 :func:`.attributes.get_history` or :meth:`.Session.is_modified`
98 which also need to know
99 the "previous" value of the attribute.
101 :param comparator_factory: a class which extends
102 :class:`.ColumnProperty.Comparator` which provides custom SQL
103 clause generation for comparison operations.
105 :param group:
106 a group name for this property when marked as deferred.
108 :param deferred:
109 when True, the column property is "deferred", meaning that
110 it does not load immediately, and is instead loaded when the
111 attribute is first accessed on an instance. See also
112 :func:`~sqlalchemy.orm.deferred`.
114 :param doc:
115 optional string that will be applied as the doc on the
116 class-bound descriptor.
118 :param expire_on_flush=True:
119 Disable expiry on flush. A column_property() which refers
120 to a SQL expression (and not a single table-bound column)
121 is considered to be a "read only" property; populating it
122 has no effect on the state of data, and it can only return
123 database state. For this reason a column_property()'s value
124 is expired whenever the parent object is involved in a
125 flush, that is, has any kind of "dirty" state within a flush.
126 Setting this parameter to ``False`` will have the effect of
127 leaving any existing value present after the flush proceeds.
128 Note however that the :class:`.Session` with default expiration
129 settings still expires
130 all attributes after a :meth:`.Session.commit` call, however.
132 :param info: Optional data dictionary which will be populated into the
133 :attr:`.MapperProperty.info` attribute of this object.
135 :param raiseload: if True, indicates the column should raise an error
136 when undeferred, rather than loading the value. This can be
137 altered at query time by using the :func:`.deferred` option with
138 raiseload=False.
140 .. versionadded:: 1.4
142 .. seealso::
144 :ref:`deferred_raiseload`
146 .. seealso::
148 :ref:`column_property_options` - to map columns while including
149 mapping options
151 :ref:`mapper_column_property_sql_expressions` - to map SQL
152 expressions
154 """
155 super(ColumnProperty, self).__init__()
156 self.columns = [
157 coercions.expect(roles.LabeledColumnExprRole, c) for c in columns
158 ]
159 self.group = kwargs.pop("group", None)
160 self.deferred = kwargs.pop("deferred", False)
161 self.raiseload = kwargs.pop("raiseload", False)
162 self.instrument = kwargs.pop("_instrument", True)
163 self.comparator_factory = kwargs.pop(
164 "comparator_factory", self.__class__.Comparator
165 )
166 self.descriptor = kwargs.pop("descriptor", None)
167 self.active_history = kwargs.pop("active_history", False)
168 self.expire_on_flush = kwargs.pop("expire_on_flush", True)
170 if "info" in kwargs:
171 self.info = kwargs.pop("info")
173 if "doc" in kwargs:
174 self.doc = kwargs.pop("doc")
175 else:
176 for col in reversed(self.columns):
177 doc = getattr(col, "doc", None)
178 if doc is not None:
179 self.doc = doc
180 break
181 else:
182 self.doc = None
184 if kwargs:
185 raise TypeError(
186 "%s received unexpected keyword argument(s): %s"
187 % (self.__class__.__name__, ", ".join(sorted(kwargs.keys())))
188 )
190 util.set_creation_order(self)
192 self.strategy_key = (
193 ("deferred", self.deferred),
194 ("instrument", self.instrument),
195 )
196 if self.raiseload:
197 self.strategy_key += (("raiseload", True),)
199 def _memoized_attr__renders_in_subqueries(self):
200 if ("query_expression", True) in self.strategy_key:
201 return self.strategy._have_default_expression
203 return ("deferred", True) not in self.strategy_key or (
204 self not in self.parent._readonly_props
205 )
207 @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
208 def _memoized_attr__deferred_column_loader(self):
209 state = util.preloaded.orm_state
210 strategies = util.preloaded.orm_strategies
211 return state.InstanceState._instance_level_callable_processor(
212 self.parent.class_manager,
213 strategies.LoadDeferredColumns(self.key),
214 self.key,
215 )
217 @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
218 def _memoized_attr__raise_column_loader(self):
219 state = util.preloaded.orm_state
220 strategies = util.preloaded.orm_strategies
221 return state.InstanceState._instance_level_callable_processor(
222 self.parent.class_manager,
223 strategies.LoadDeferredColumns(self.key, True),
224 self.key,
225 )
227 def __clause_element__(self):
228 """Allow the ColumnProperty to work in expression before it is turned
229 into an instrumented attribute.
230 """
232 return self.expression
234 @property
235 def expression(self):
236 """Return the primary column or expression for this ColumnProperty.
238 E.g.::
241 class File(Base):
242 # ...
244 name = Column(String(64))
245 extension = Column(String(8))
246 filename = column_property(name + '.' + extension)
247 path = column_property('C:/' + filename.expression)
249 .. seealso::
251 :ref:`mapper_column_property_sql_expressions_composed`
253 """
254 return self.columns[0]
256 def instrument_class(self, mapper):
257 if not self.instrument:
258 return
260 attributes.register_descriptor(
261 mapper.class_,
262 self.key,
263 comparator=self.comparator_factory(self, mapper),
264 parententity=mapper,
265 doc=self.doc,
266 )
268 def do_init(self):
269 super(ColumnProperty, self).do_init()
271 if len(self.columns) > 1 and set(self.parent.primary_key).issuperset(
272 self.columns
273 ):
274 util.warn(
275 (
276 "On mapper %s, primary key column '%s' is being combined "
277 "with distinct primary key column '%s' in attribute '%s'. "
278 "Use explicit properties to give each column its own "
279 "mapped attribute name."
280 )
281 % (self.parent, self.columns[1], self.columns[0], self.key)
282 )
284 def copy(self):
285 return ColumnProperty(
286 deferred=self.deferred,
287 group=self.group,
288 active_history=self.active_history,
289 *self.columns
290 )
292 def _getcommitted(
293 self, state, dict_, column, passive=attributes.PASSIVE_OFF
294 ):
295 return state.get_impl(self.key).get_committed_value(
296 state, dict_, passive=passive
297 )
299 def merge(
300 self,
301 session,
302 source_state,
303 source_dict,
304 dest_state,
305 dest_dict,
306 load,
307 _recursive,
308 _resolve_conflict_map,
309 ):
310 if not self.instrument:
311 return
312 elif self.key in source_dict:
313 value = source_dict[self.key]
315 if not load:
316 dest_dict[self.key] = value
317 else:
318 impl = dest_state.get_impl(self.key)
319 impl.set(dest_state, dest_dict, value, None)
320 elif dest_state.has_identity and self.key not in dest_dict:
321 dest_state._expire_attributes(
322 dest_dict, [self.key], no_loader=True
323 )
325 class Comparator(util.MemoizedSlots, PropComparator):
326 """Produce boolean, comparison, and other operators for
327 :class:`.ColumnProperty` attributes.
329 See the documentation for :class:`.PropComparator` for a brief
330 overview.
332 .. seealso::
334 :class:`.PropComparator`
336 :class:`.ColumnOperators`
338 :ref:`types_operators`
340 :attr:`.TypeEngine.comparator_factory`
342 """
344 __slots__ = "__clause_element__", "info", "expressions"
346 def _orm_annotate_column(self, column):
347 """annotate and possibly adapt a column to be returned
348 as the mapped-attribute exposed version of the column.
350 The column in this context needs to act as much like the
351 column in an ORM mapped context as possible, so includes
352 annotations to give hints to various ORM functions as to
353 the source entity of this column. It also adapts it
354 to the mapper's with_polymorphic selectable if one is
355 present.
357 """
359 pe = self._parententity
360 annotations = {
361 "entity_namespace": pe,
362 "parententity": pe,
363 "parentmapper": pe,
364 "proxy_key": self.prop.key,
365 }
367 col = column
369 # for a mapper with polymorphic_on and an adapter, return
370 # the column against the polymorphic selectable.
371 # see also orm.util._orm_downgrade_polymorphic_columns
372 # for the reverse operation.
373 if self._parentmapper._polymorphic_adapter:
374 mapper_local_col = col
375 col = self._parentmapper._polymorphic_adapter.traverse(col)
377 # this is a clue to the ORM Query etc. that this column
378 # was adapted to the mapper's polymorphic_adapter. the
379 # ORM uses this hint to know which column its adapting.
380 annotations["adapt_column"] = mapper_local_col
382 return col._annotate(annotations)._set_propagate_attrs(
383 {"compile_state_plugin": "orm", "plugin_subject": pe}
384 )
386 def _memoized_method___clause_element__(self):
387 if self.adapter:
388 return self.adapter(self.prop.columns[0], self.prop.key)
389 else:
390 return self._orm_annotate_column(self.prop.columns[0])
392 def _memoized_attr_info(self):
393 """The .info dictionary for this attribute."""
395 ce = self.__clause_element__()
396 try:
397 return ce.info
398 except AttributeError:
399 return self.prop.info
401 def _memoized_attr_expressions(self):
402 """The full sequence of columns referenced by this
403 attribute, adjusted for any aliasing in progress.
405 .. versionadded:: 1.3.17
407 """
408 if self.adapter:
409 return [
410 self.adapter(col, self.prop.key)
411 for col in self.prop.columns
412 ]
413 else:
414 return [
415 self._orm_annotate_column(col) for col in self.prop.columns
416 ]
418 def _fallback_getattr(self, key):
419 """proxy attribute access down to the mapped column.
421 this allows user-defined comparison methods to be accessed.
422 """
423 return getattr(self.__clause_element__(), key)
425 def operate(self, op, *other, **kwargs):
426 return op(self.__clause_element__(), *other, **kwargs)
428 def reverse_operate(self, op, other, **kwargs):
429 col = self.__clause_element__()
430 return op(col._bind_param(op, other), col, **kwargs)
432 def __str__(self):
433 return str(self.parent.class_.__name__) + "." + self.key