properties.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. # orm/properties.py
  2. # Copyright (C) 2005-2017 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
  7. """MapperProperty implementations.
  8. This is a private module which defines the behavior of invidual ORM-
  9. mapped attributes.
  10. """
  11. from __future__ import absolute_import
  12. from .. import util, log
  13. from ..sql import expression
  14. from . import attributes
  15. from .util import _orm_full_deannotate
  16. from .interfaces import PropComparator, StrategizedProperty
  17. __all__ = ['ColumnProperty', 'CompositeProperty', 'SynonymProperty',
  18. 'ComparableProperty', 'RelationshipProperty']
  19. @log.class_logger
  20. class ColumnProperty(StrategizedProperty):
  21. """Describes an object attribute that corresponds to a table column.
  22. Public constructor is the :func:`.orm.column_property` function.
  23. """
  24. strategy_wildcard_key = 'column'
  25. __slots__ = (
  26. '_orig_columns', 'columns', 'group', 'deferred',
  27. 'instrument', 'comparator_factory', 'descriptor', 'extension',
  28. 'active_history', 'expire_on_flush', 'info', 'doc',
  29. 'strategy_key', '_creation_order', '_is_polymorphic_discriminator',
  30. '_mapped_by_synonym', '_deferred_column_loader')
  31. def __init__(self, *columns, **kwargs):
  32. r"""Provide a column-level property for use with a Mapper.
  33. Column-based properties can normally be applied to the mapper's
  34. ``properties`` dictionary using the :class:`.Column` element directly.
  35. Use this function when the given column is not directly present within
  36. the mapper's selectable; examples include SQL expressions, functions,
  37. and scalar SELECT queries.
  38. Columns that aren't present in the mapper's selectable won't be
  39. persisted by the mapper and are effectively "read-only" attributes.
  40. :param \*cols:
  41. list of Column objects to be mapped.
  42. :param active_history=False:
  43. When ``True``, indicates that the "previous" value for a
  44. scalar attribute should be loaded when replaced, if not
  45. already loaded. Normally, history tracking logic for
  46. simple non-primary-key scalar values only needs to be
  47. aware of the "new" value in order to perform a flush. This
  48. flag is available for applications that make use of
  49. :func:`.attributes.get_history` or :meth:`.Session.is_modified`
  50. which also need to know
  51. the "previous" value of the attribute.
  52. .. versionadded:: 0.6.6
  53. :param comparator_factory: a class which extends
  54. :class:`.ColumnProperty.Comparator` which provides custom SQL
  55. clause generation for comparison operations.
  56. :param group:
  57. a group name for this property when marked as deferred.
  58. :param deferred:
  59. when True, the column property is "deferred", meaning that
  60. it does not load immediately, and is instead loaded when the
  61. attribute is first accessed on an instance. See also
  62. :func:`~sqlalchemy.orm.deferred`.
  63. :param doc:
  64. optional string that will be applied as the doc on the
  65. class-bound descriptor.
  66. :param expire_on_flush=True:
  67. Disable expiry on flush. A column_property() which refers
  68. to a SQL expression (and not a single table-bound column)
  69. is considered to be a "read only" property; populating it
  70. has no effect on the state of data, and it can only return
  71. database state. For this reason a column_property()'s value
  72. is expired whenever the parent object is involved in a
  73. flush, that is, has any kind of "dirty" state within a flush.
  74. Setting this parameter to ``False`` will have the effect of
  75. leaving any existing value present after the flush proceeds.
  76. Note however that the :class:`.Session` with default expiration
  77. settings still expires
  78. all attributes after a :meth:`.Session.commit` call, however.
  79. .. versionadded:: 0.7.3
  80. :param info: Optional data dictionary which will be populated into the
  81. :attr:`.MapperProperty.info` attribute of this object.
  82. .. versionadded:: 0.8
  83. :param extension:
  84. an
  85. :class:`.AttributeExtension`
  86. instance, or list of extensions, which will be prepended
  87. to the list of attribute listeners for the resulting
  88. descriptor placed on the class.
  89. **Deprecated.** Please see :class:`.AttributeEvents`.
  90. """
  91. super(ColumnProperty, self).__init__()
  92. self._orig_columns = [expression._labeled(c) for c in columns]
  93. self.columns = [expression._labeled(_orm_full_deannotate(c))
  94. for c in columns]
  95. self.group = kwargs.pop('group', None)
  96. self.deferred = kwargs.pop('deferred', False)
  97. self.instrument = kwargs.pop('_instrument', True)
  98. self.comparator_factory = kwargs.pop('comparator_factory',
  99. self.__class__.Comparator)
  100. self.descriptor = kwargs.pop('descriptor', None)
  101. self.extension = kwargs.pop('extension', None)
  102. self.active_history = kwargs.pop('active_history', False)
  103. self.expire_on_flush = kwargs.pop('expire_on_flush', True)
  104. if 'info' in kwargs:
  105. self.info = kwargs.pop('info')
  106. if 'doc' in kwargs:
  107. self.doc = kwargs.pop('doc')
  108. else:
  109. for col in reversed(self.columns):
  110. doc = getattr(col, 'doc', None)
  111. if doc is not None:
  112. self.doc = doc
  113. break
  114. else:
  115. self.doc = None
  116. if kwargs:
  117. raise TypeError(
  118. "%s received unexpected keyword argument(s): %s" % (
  119. self.__class__.__name__,
  120. ', '.join(sorted(kwargs.keys()))))
  121. util.set_creation_order(self)
  122. self.strategy_key = (
  123. ("deferred", self.deferred),
  124. ("instrument", self.instrument)
  125. )
  126. @util.dependencies("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
  127. def _memoized_attr__deferred_column_loader(self, state, strategies):
  128. return state.InstanceState._instance_level_callable_processor(
  129. self.parent.class_manager,
  130. strategies.LoadDeferredColumns(self.key), self.key)
  131. @property
  132. def expression(self):
  133. """Return the primary column or expression for this ColumnProperty.
  134. """
  135. return self.columns[0]
  136. def instrument_class(self, mapper):
  137. if not self.instrument:
  138. return
  139. attributes.register_descriptor(
  140. mapper.class_,
  141. self.key,
  142. comparator=self.comparator_factory(self, mapper),
  143. parententity=mapper,
  144. doc=self.doc
  145. )
  146. def do_init(self):
  147. super(ColumnProperty, self).do_init()
  148. if len(self.columns) > 1 and \
  149. set(self.parent.primary_key).issuperset(self.columns):
  150. util.warn(
  151. ("On mapper %s, primary key column '%s' is being combined "
  152. "with distinct primary key column '%s' in attribute '%s'. "
  153. "Use explicit properties to give each column its own mapped "
  154. "attribute name.") % (self.parent, self.columns[1],
  155. self.columns[0], self.key))
  156. def copy(self):
  157. return ColumnProperty(
  158. deferred=self.deferred,
  159. group=self.group,
  160. active_history=self.active_history,
  161. *self.columns)
  162. def _getcommitted(self, state, dict_, column,
  163. passive=attributes.PASSIVE_OFF):
  164. return state.get_impl(self.key).\
  165. get_committed_value(state, dict_, passive=passive)
  166. def merge(self, session, source_state, source_dict, dest_state,
  167. dest_dict, load, _recursive, _resolve_conflict_map):
  168. if not self.instrument:
  169. return
  170. elif self.key in source_dict:
  171. value = source_dict[self.key]
  172. if not load:
  173. dest_dict[self.key] = value
  174. else:
  175. impl = dest_state.get_impl(self.key)
  176. impl.set(dest_state, dest_dict, value, None)
  177. elif dest_state.has_identity and self.key not in dest_dict:
  178. dest_state._expire_attributes(
  179. dest_dict, [self.key], no_loader=True)
  180. class Comparator(util.MemoizedSlots, PropComparator):
  181. """Produce boolean, comparison, and other operators for
  182. :class:`.ColumnProperty` attributes.
  183. See the documentation for :class:`.PropComparator` for a brief
  184. overview.
  185. See also:
  186. :class:`.PropComparator`
  187. :class:`.ColumnOperators`
  188. :ref:`types_operators`
  189. :attr:`.TypeEngine.comparator_factory`
  190. """
  191. __slots__ = '__clause_element__', 'info'
  192. def _memoized_method___clause_element__(self):
  193. if self.adapter:
  194. return self.adapter(self.prop.columns[0])
  195. else:
  196. # no adapter, so we aren't aliased
  197. # assert self._parententity is self._parentmapper
  198. return self.prop.columns[0]._annotate({
  199. "parententity": self._parententity,
  200. "parentmapper": self._parententity})
  201. def _memoized_attr_info(self):
  202. ce = self.__clause_element__()
  203. try:
  204. return ce.info
  205. except AttributeError:
  206. return self.prop.info
  207. def _fallback_getattr(self, key):
  208. """proxy attribute access down to the mapped column.
  209. this allows user-defined comparison methods to be accessed.
  210. """
  211. return getattr(self.__clause_element__(), key)
  212. def operate(self, op, *other, **kwargs):
  213. return op(self.__clause_element__(), *other, **kwargs)
  214. def reverse_operate(self, op, other, **kwargs):
  215. col = self.__clause_element__()
  216. return op(col._bind_param(op, other), col, **kwargs)
  217. def __str__(self):
  218. return str(self.parent.class_.__name__) + "." + self.key