| Viewing file:  properties.py (10.18 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# orm/properties.py# Copyright (C) 2005-2016 the SQLAlchemy authors and contributors
 # <see AUTHORS file>
 #
 # This module is part of SQLAlchemy and is released under
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 """MapperProperty implementations.
 
 This is a private module which defines the behavior of invidual ORM-
 mapped attributes.
 
 """
 from __future__ import absolute_import
 
 from .. import util, log
 from ..sql import expression
 from . import attributes
 from .util import _orm_full_deannotate
 
 from .interfaces import PropComparator, StrategizedProperty
 
 __all__ = ['ColumnProperty', 'CompositeProperty', 'SynonymProperty',
 'ComparableProperty', 'RelationshipProperty']
 
 
 @log.class_logger
 class ColumnProperty(StrategizedProperty):
 """Describes an object attribute that corresponds to a table column.
 
 Public constructor is the :func:`.orm.column_property` function.
 
 """
 
 strategy_wildcard_key = 'column'
 
 __slots__ = (
 '_orig_columns', 'columns', 'group', 'deferred',
 'instrument', 'comparator_factory', 'descriptor', 'extension',
 'active_history', 'expire_on_flush', 'info', 'doc',
 'strategy_class', '_creation_order', '_is_polymorphic_discriminator',
 '_mapped_by_synonym', '_deferred_column_loader')
 
 def __init__(self, *columns, **kwargs):
 """Provide a column-level property for use with a Mapper.
 
 Column-based properties can normally be applied to the mapper's
 ``properties`` dictionary using the :class:`.Column` element directly.
 Use this function when the given column is not directly present within
 the mapper's selectable; examples include SQL expressions, functions,
 and scalar SELECT queries.
 
 Columns that aren't present in the mapper's selectable won't be
 persisted by the mapper and are effectively "read-only" attributes.
 
 :param \*cols:
 list of Column objects to be mapped.
 
 :param active_history=False:
 When ``True``, indicates that the "previous" value for a
 scalar attribute should be loaded when replaced, if not
 already loaded. Normally, history tracking logic for
 simple non-primary-key scalar values only needs to be
 aware of the "new" value in order to perform a flush. This
 flag is available for applications that make use of
 :func:`.attributes.get_history` or :meth:`.Session.is_modified`
 which also need to know
 the "previous" value of the attribute.
 
 .. versionadded:: 0.6.6
 
 :param comparator_factory: a class which extends
 :class:`.ColumnProperty.Comparator` which provides custom SQL
 clause generation for comparison operations.
 
 :param group:
 a group name for this property when marked as deferred.
 
 :param deferred:
 when True, the column property is "deferred", meaning that
 it does not load immediately, and is instead loaded when the
 attribute is first accessed on an instance.  See also
 :func:`~sqlalchemy.orm.deferred`.
 
 :param doc:
 optional string that will be applied as the doc on the
 class-bound descriptor.
 
 :param expire_on_flush=True:
 Disable expiry on flush.   A column_property() which refers
 to a SQL expression (and not a single table-bound column)
 is considered to be a "read only" property; populating it
 has no effect on the state of data, and it can only return
 database state.   For this reason a column_property()'s value
 is expired whenever the parent object is involved in a
 flush, that is, has any kind of "dirty" state within a flush.
 Setting this parameter to ``False`` will have the effect of
 leaving any existing value present after the flush proceeds.
 Note however that the :class:`.Session` with default expiration
 settings still expires
 all attributes after a :meth:`.Session.commit` call, however.
 
 .. versionadded:: 0.7.3
 
 :param info: Optional data dictionary which will be populated into the
 :attr:`.MapperProperty.info` attribute of this object.
 
 .. versionadded:: 0.8
 
 :param extension:
 an
 :class:`.AttributeExtension`
 instance, or list of extensions, which will be prepended
 to the list of attribute listeners for the resulting
 descriptor placed on the class.
 **Deprecated.** Please see :class:`.AttributeEvents`.
 
 """
 super(ColumnProperty, self).__init__()
 self._orig_columns = [expression._labeled(c) for c in columns]
 self.columns = [expression._labeled(_orm_full_deannotate(c))
 for c in columns]
 self.group = kwargs.pop('group', None)
 self.deferred = kwargs.pop('deferred', False)
 self.instrument = kwargs.pop('_instrument', True)
 self.comparator_factory = kwargs.pop('comparator_factory',
 self.__class__.Comparator)
 self.descriptor = kwargs.pop('descriptor', None)
 self.extension = kwargs.pop('extension', None)
 self.active_history = kwargs.pop('active_history', False)
 self.expire_on_flush = kwargs.pop('expire_on_flush', True)
 
 if 'info' in kwargs:
 self.info = kwargs.pop('info')
 
 if 'doc' in kwargs:
 self.doc = kwargs.pop('doc')
 else:
 for col in reversed(self.columns):
 doc = getattr(col, 'doc', None)
 if doc is not None:
 self.doc = doc
 break
 else:
 self.doc = None
 
 if kwargs:
 raise TypeError(
 "%s received unexpected keyword argument(s): %s" % (
 self.__class__.__name__,
 ', '.join(sorted(kwargs.keys()))))
 
 util.set_creation_order(self)
 
 self.strategy_class = self._strategy_lookup(
 ("deferred", self.deferred),
 ("instrument", self.instrument)
 )
 
 @util.dependencies("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
 def _memoized_attr__deferred_column_loader(self, state, strategies):
 return state.InstanceState._instance_level_callable_processor(
 self.parent.class_manager,
 strategies.LoadDeferredColumns(self.key), self.key)
 
 @property
 def expression(self):
 """Return the primary column or expression for this ColumnProperty.
 
 """
 return self.columns[0]
 
 def instrument_class(self, mapper):
 if not self.instrument:
 return
 
 attributes.register_descriptor(
 mapper.class_,
 self.key,
 comparator=self.comparator_factory(self, mapper),
 parententity=mapper,
 doc=self.doc
 )
 
 def do_init(self):
 super(ColumnProperty, self).do_init()
 if len(self.columns) > 1 and \
 set(self.parent.primary_key).issuperset(self.columns):
 util.warn(
 ("On mapper %s, primary key column '%s' is being combined "
 "with distinct primary key column '%s' in attribute '%s'.  "
 "Use explicit properties to give each column its own mapped "
 "attribute name.") % (self.parent, self.columns[1],
 self.columns[0], self.key))
 
 def copy(self):
 return ColumnProperty(
 deferred=self.deferred,
 group=self.group,
 active_history=self.active_history,
 *self.columns)
 
 def _getcommitted(self, state, dict_, column,
 passive=attributes.PASSIVE_OFF):
 return state.get_impl(self.key).\
 get_committed_value(state, dict_, passive=passive)
 
 def merge(self, session, source_state, source_dict, dest_state,
 dest_dict, load, _recursive):
 if not self.instrument:
 return
 elif self.key in source_dict:
 value = source_dict[self.key]
 
 if not load:
 dest_dict[self.key] = value
 else:
 impl = dest_state.get_impl(self.key)
 impl.set(dest_state, dest_dict, value, None)
 elif dest_state.has_identity and self.key not in dest_dict:
 dest_state._expire_attributes(dest_dict, [self.key])
 
 class Comparator(util.MemoizedSlots, PropComparator):
 """Produce boolean, comparison, and other operators for
 :class:`.ColumnProperty` attributes.
 
 See the documentation for :class:`.PropComparator` for a brief
 overview.
 
 See also:
 
 :class:`.PropComparator`
 
 :class:`.ColumnOperators`
 
 :ref:`types_operators`
 
 :attr:`.TypeEngine.comparator_factory`
 
 """
 
 __slots__ = '__clause_element__', 'info'
 
 def _memoized_method___clause_element__(self):
 if self.adapter:
 return self.adapter(self.prop.columns[0])
 else:
 # no adapter, so we aren't aliased
 # assert self._parententity is self._parentmapper
 return self.prop.columns[0]._annotate({
 "parententity": self._parententity,
 "parentmapper": self._parententity})
 
 def _memoized_attr_info(self):
 ce = self.__clause_element__()
 try:
 return ce.info
 except AttributeError:
 return self.prop.info
 
 def _fallback_getattr(self, key):
 """proxy attribute access down to the mapped column.
 
 this allows user-defined comparison methods to be accessed.
 """
 return getattr(self.__clause_element__(), key)
 
 def operate(self, op, *other, **kwargs):
 return op(self.__clause_element__(), *other, **kwargs)
 
 def reverse_operate(self, op, other, **kwargs):
 col = self.__clause_element__()
 return op(col._bind_param(op, other), col, **kwargs)
 
 def __str__(self):
 return str(self.parent.class_.__name__) + "." + self.key
 
 |