dibbler/sqlalchemy/orm/properties.py

278 lines
10 KiB
Python
Raw Normal View History

2017-04-15 18:27:12 +02:00
# orm/properties.py
# Copyright (C) 2005-2017 the SQLAlchemy authors and contributors
# <see AUTHORS file>
2010-05-07 19:33:49 +02:00
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""MapperProperty implementations.
2017-04-15 18:27:12 +02:00
This is a private module which defines the behavior of invidual ORM-
mapped attributes.
2010-05-07 19:33:49 +02:00
"""
2017-04-15 18:27:12 +02:00
from __future__ import absolute_import
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
from .. import util, log
from ..sql import expression
from . import attributes
from .util import _orm_full_deannotate
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
from .interfaces import PropComparator, StrategizedProperty
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
__all__ = ['ColumnProperty', 'CompositeProperty', 'SynonymProperty',
'ComparableProperty', 'RelationshipProperty']
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
@log.class_logger
2010-05-07 19:33:49 +02:00
class ColumnProperty(StrategizedProperty):
2017-04-15 18:27:12 +02:00
"""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_key', '_creation_order', '_is_polymorphic_discriminator',
'_mapped_by_synonym', '_deferred_column_loader')
2010-05-07 19:33:49 +02:00
def __init__(self, *columns, **kwargs):
2017-04-15 18:27:12 +02:00
r"""Provide a column-level property for use with a Mapper.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
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.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
Columns that aren't present in the mapper's selectable won't be
persisted by the mapper and are effectively "read-only" attributes.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
:param \*cols:
list of Column objects to be mapped.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
: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.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
.. 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
2010-05-07 19:33:49 +02:00
:param extension:
2017-04-15 18:27:12 +02:00
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`.
2010-05-07 19:33:49 +02:00
"""
2017-04-15 18:27:12 +02:00
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]
2010-05-07 19:33:49 +02:00
self.group = kwargs.pop('group', None)
self.deferred = kwargs.pop('deferred', False)
self.instrument = kwargs.pop('_instrument', True)
2017-04-15 18:27:12 +02:00
self.comparator_factory = kwargs.pop('comparator_factory',
self.__class__.Comparator)
2010-05-07 19:33:49 +02:00
self.descriptor = kwargs.pop('descriptor', None)
self.extension = kwargs.pop('extension', None)
2017-04-15 18:27:12 +02:00
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
2010-05-07 19:33:49 +02:00
if kwargs:
raise TypeError(
"%s received unexpected keyword argument(s): %s" % (
2017-04-15 18:27:12 +02:00
self.__class__.__name__,
', '.join(sorted(kwargs.keys()))))
2010-05-07 19:33:49 +02:00
util.set_creation_order(self)
2017-04-15 18:27:12 +02:00
self.strategy_key = (
("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]
2010-05-07 19:33:49 +02:00
def instrument_class(self, mapper):
if not self.instrument:
return
2017-04-15 18:27:12 +02:00
2010-05-07 19:33:49 +02:00
attributes.register_descriptor(
2017-04-15 18:27:12 +02:00
mapper.class_,
self.key,
comparator=self.comparator_factory(self, mapper),
2010-05-07 19:33:49 +02:00
parententity=mapper,
2017-04-15 18:27:12 +02:00
doc=self.doc
)
2010-05-07 19:33:49 +02:00
def do_init(self):
super(ColumnProperty, self).do_init()
2017-04-15 18:27:12 +02:00
if len(self.columns) > 1 and \
set(self.parent.primary_key).issuperset(self.columns):
2010-05-07 19:33:49 +02:00
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):
2017-04-15 18:27:12 +02:00
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, _resolve_conflict_map):
if not self.instrument:
return
elif self.key in source_dict:
2010-05-07 19:33:49 +02:00
value = source_dict[self.key]
2017-04-15 18:27:12 +02:00
2010-05-07 19:33:49 +02:00
if not load:
dest_dict[self.key] = value
else:
impl = dest_state.get_impl(self.key)
impl.set(dest_state, dest_dict, value, None)
2017-04-15 18:27:12 +02:00
elif dest_state.has_identity and self.key not in dest_dict:
dest_state._expire_attributes(
dest_dict, [self.key], no_loader=True)
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
class Comparator(util.MemoizedSlots, PropComparator):
"""Produce boolean, comparison, and other operators for
:class:`.ColumnProperty` attributes.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
See the documentation for :class:`.PropComparator` for a brief
overview.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
See also:
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
:class:`.PropComparator`
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
:class:`.ColumnOperators`
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
:ref:`types_operators`
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
:attr:`.TypeEngine.comparator_factory`
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
"""
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
__slots__ = '__clause_element__', 'info'
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
def _memoized_method___clause_element__(self):
2010-05-07 19:33:49 +02:00
if self.adapter:
2017-04-15 18:27:12 +02:00
return self.adapter(self.prop.columns[0])
2010-05-07 19:33:49 +02:00
else:
2017-04-15 18:27:12 +02:00
# 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
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
def _fallback_getattr(self, key):
"""proxy attribute access down to the mapped column.
2010-05-07 19:33:49 +02:00
2017-04-15 18:27:12 +02:00
this allows user-defined comparison methods to be accessed.
2010-05-07 19:33:49 +02:00
"""
2017-04-15 18:27:12 +02:00
return getattr(self.__clause_element__(), key)
2010-05-07 19:33:49 +02:00
def operate(self, op, *other, **kwargs):
2017-04-15 18:27:12 +02:00
return op(self.__clause_element__(), *other, **kwargs)
2010-05-07 19:33:49 +02:00
def reverse_operate(self, op, other, **kwargs):
2017-04-15 18:27:12 +02:00
col = self.__clause_element__()
return op(col._bind_param(op, other), col, **kwargs)
2010-05-07 19:33:49 +02:00
def __str__(self):
return str(self.parent.class_.__name__) + "." + self.key