You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							439 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
	
	
							439 lines
						
					
					
						
							15 KiB
						
					
					
				# orm/properties.py
 | 
						|
# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
 | 
						|
# <see AUTHORS file>
 | 
						|
#
 | 
						|
# This module is part of SQLAlchemy and is released under
 | 
						|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
 | 
						|
 | 
						|
"""MapperProperty implementations.
 | 
						|
 | 
						|
This is a private module which defines the behavior of individual ORM-
 | 
						|
mapped attributes.
 | 
						|
 | 
						|
"""
 | 
						|
from __future__ import absolute_import
 | 
						|
 | 
						|
from . import attributes
 | 
						|
from .descriptor_props import CompositeProperty
 | 
						|
from .descriptor_props import ConcreteInheritedProperty
 | 
						|
from .descriptor_props import SynonymProperty
 | 
						|
from .interfaces import PropComparator
 | 
						|
from .interfaces import StrategizedProperty
 | 
						|
from .relationships import RelationshipProperty
 | 
						|
from .util import _orm_full_deannotate
 | 
						|
from .. import log
 | 
						|
from .. import util
 | 
						|
from ..sql import coercions
 | 
						|
from ..sql import roles
 | 
						|
 | 
						|
 | 
						|
__all__ = [
 | 
						|
    "ColumnProperty",
 | 
						|
    "CompositeProperty",
 | 
						|
    "ConcreteInheritedProperty",
 | 
						|
    "RelationshipProperty",
 | 
						|
    "SynonymProperty",
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
@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"
 | 
						|
    inherit_cache = True
 | 
						|
    _links_to_entity = False
 | 
						|
 | 
						|
    __slots__ = (
 | 
						|
        "_orig_columns",
 | 
						|
        "columns",
 | 
						|
        "group",
 | 
						|
        "deferred",
 | 
						|
        "instrument",
 | 
						|
        "comparator_factory",
 | 
						|
        "descriptor",
 | 
						|
        "active_history",
 | 
						|
        "expire_on_flush",
 | 
						|
        "info",
 | 
						|
        "doc",
 | 
						|
        "strategy_key",
 | 
						|
        "_creation_order",
 | 
						|
        "_is_polymorphic_discriminator",
 | 
						|
        "_mapped_by_synonym",
 | 
						|
        "_deferred_column_loader",
 | 
						|
        "_raise_column_loader",
 | 
						|
        "_renders_in_subqueries",
 | 
						|
        "raiseload",
 | 
						|
    )
 | 
						|
 | 
						|
    def __init__(self, *columns, **kwargs):
 | 
						|
        r"""Provide a column-level property for use with a mapping.
 | 
						|
 | 
						|
        Column-based properties can normally be applied to the mapper's
 | 
						|
        ``properties`` dictionary using the :class:`_schema.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.
 | 
						|
 | 
						|
        The :func:`_orm.column_property` function returns an instance of
 | 
						|
        :class:`.ColumnProperty`.
 | 
						|
 | 
						|
        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.
 | 
						|
 | 
						|
        :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.
 | 
						|
 | 
						|
        :param info: Optional data dictionary which will be populated into the
 | 
						|
            :attr:`.MapperProperty.info` attribute of this object.
 | 
						|
 | 
						|
        :param raiseload: if True, indicates the column should raise an error
 | 
						|
            when undeferred, rather than loading the value.  This can be
 | 
						|
            altered at query time by using the :func:`.deferred` option with
 | 
						|
            raiseload=False.
 | 
						|
 | 
						|
            .. versionadded:: 1.4
 | 
						|
 | 
						|
            .. seealso::
 | 
						|
 | 
						|
                :ref:`deferred_raiseload`
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`column_property_options` - to map columns while including
 | 
						|
            mapping options
 | 
						|
 | 
						|
            :ref:`mapper_column_property_sql_expressions` - to map SQL
 | 
						|
            expressions
 | 
						|
 | 
						|
        """
 | 
						|
        super(ColumnProperty, self).__init__()
 | 
						|
        self._orig_columns = [
 | 
						|
            coercions.expect(roles.LabeledColumnExprRole, c) for c in columns
 | 
						|
        ]
 | 
						|
        self.columns = [
 | 
						|
            coercions.expect(
 | 
						|
                roles.LabeledColumnExprRole, _orm_full_deannotate(c)
 | 
						|
            )
 | 
						|
            for c in columns
 | 
						|
        ]
 | 
						|
        self.group = kwargs.pop("group", None)
 | 
						|
        self.deferred = kwargs.pop("deferred", False)
 | 
						|
        self.raiseload = kwargs.pop("raiseload", False)
 | 
						|
        self.instrument = kwargs.pop("_instrument", True)
 | 
						|
        self.comparator_factory = kwargs.pop(
 | 
						|
            "comparator_factory", self.__class__.Comparator
 | 
						|
        )
 | 
						|
        self.descriptor = kwargs.pop("descriptor", 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_key = (
 | 
						|
            ("deferred", self.deferred),
 | 
						|
            ("instrument", self.instrument),
 | 
						|
        )
 | 
						|
        if self.raiseload:
 | 
						|
            self.strategy_key += (("raiseload", True),)
 | 
						|
 | 
						|
    def _memoized_attr__renders_in_subqueries(self):
 | 
						|
        return ("deferred", True) not in self.strategy_key or (
 | 
						|
            self not in self.parent._readonly_props
 | 
						|
        )
 | 
						|
 | 
						|
    @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
 | 
						|
    def _memoized_attr__deferred_column_loader(self):
 | 
						|
        state = util.preloaded.orm_state
 | 
						|
        strategies = util.preloaded.orm_strategies
 | 
						|
        return state.InstanceState._instance_level_callable_processor(
 | 
						|
            self.parent.class_manager,
 | 
						|
            strategies.LoadDeferredColumns(self.key),
 | 
						|
            self.key,
 | 
						|
        )
 | 
						|
 | 
						|
    @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
 | 
						|
    def _memoized_attr__raise_column_loader(self):
 | 
						|
        state = util.preloaded.orm_state
 | 
						|
        strategies = util.preloaded.orm_strategies
 | 
						|
        return state.InstanceState._instance_level_callable_processor(
 | 
						|
            self.parent.class_manager,
 | 
						|
            strategies.LoadDeferredColumns(self.key, True),
 | 
						|
            self.key,
 | 
						|
        )
 | 
						|
 | 
						|
    def __clause_element__(self):
 | 
						|
        """Allow the ColumnProperty to work in expression before it is turned
 | 
						|
        into an instrumented attribute.
 | 
						|
        """
 | 
						|
 | 
						|
        return self.expression
 | 
						|
 | 
						|
    @property
 | 
						|
    def expression(self):
 | 
						|
        """Return the primary column or expression for this ColumnProperty.
 | 
						|
 | 
						|
        E.g.::
 | 
						|
 | 
						|
 | 
						|
            class File(Base):
 | 
						|
                # ...
 | 
						|
 | 
						|
                name = Column(String(64))
 | 
						|
                extension = Column(String(8))
 | 
						|
                filename = column_property(name + '.' + extension)
 | 
						|
                path = column_property('C:/' + filename.expression)
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`mapper_column_property_sql_expressions_composed`
 | 
						|
 | 
						|
        """
 | 
						|
        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,
 | 
						|
        _resolve_conflict_map,
 | 
						|
    ):
 | 
						|
        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], no_loader=True
 | 
						|
            )
 | 
						|
 | 
						|
    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.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :class:`.PropComparator`
 | 
						|
 | 
						|
            :class:`.ColumnOperators`
 | 
						|
 | 
						|
            :ref:`types_operators`
 | 
						|
 | 
						|
            :attr:`.TypeEngine.comparator_factory`
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        __slots__ = "__clause_element__", "info", "expressions"
 | 
						|
 | 
						|
        def _orm_annotate_column(self, column):
 | 
						|
            """annotate and possibly adapt a column to be returned
 | 
						|
            as the mapped-attribute exposed version of the column.
 | 
						|
 | 
						|
            The column in this context needs to act as much like the
 | 
						|
            column in an ORM mapped context as possible, so includes
 | 
						|
            annotations to give hints to various ORM functions as to
 | 
						|
            the source entity of this column.   It also adapts it
 | 
						|
            to the mapper's with_polymorphic selectable if one is
 | 
						|
            present.
 | 
						|
 | 
						|
            """
 | 
						|
 | 
						|
            pe = self._parententity
 | 
						|
            annotations = {
 | 
						|
                "entity_namespace": pe,
 | 
						|
                "parententity": pe,
 | 
						|
                "parentmapper": pe,
 | 
						|
                "proxy_key": self.prop.key,
 | 
						|
            }
 | 
						|
 | 
						|
            col = column
 | 
						|
 | 
						|
            # for a mapper with polymorphic_on and an adapter, return
 | 
						|
            # the column against the polymorphic selectable.
 | 
						|
            # see also orm.util._orm_downgrade_polymorphic_columns
 | 
						|
            # for the reverse operation.
 | 
						|
            if self._parentmapper._polymorphic_adapter:
 | 
						|
                mapper_local_col = col
 | 
						|
                col = self._parentmapper._polymorphic_adapter.traverse(col)
 | 
						|
 | 
						|
                # this is a clue to the ORM Query etc. that this column
 | 
						|
                # was adapted to the mapper's polymorphic_adapter.  the
 | 
						|
                # ORM uses this hint to know which column its adapting.
 | 
						|
                annotations["adapt_column"] = mapper_local_col
 | 
						|
 | 
						|
            return col._annotate(annotations)._set_propagate_attrs(
 | 
						|
                {"compile_state_plugin": "orm", "plugin_subject": pe}
 | 
						|
            )
 | 
						|
 | 
						|
        def _memoized_method___clause_element__(self):
 | 
						|
            if self.adapter:
 | 
						|
                return self.adapter(self.prop.columns[0], self.prop.key)
 | 
						|
            else:
 | 
						|
                return self._orm_annotate_column(self.prop.columns[0])
 | 
						|
 | 
						|
        def _memoized_attr_info(self):
 | 
						|
            """The .info dictionary for this attribute."""
 | 
						|
 | 
						|
            ce = self.__clause_element__()
 | 
						|
            try:
 | 
						|
                return ce.info
 | 
						|
            except AttributeError:
 | 
						|
                return self.prop.info
 | 
						|
 | 
						|
        def _memoized_attr_expressions(self):
 | 
						|
            """The full sequence of columns referenced by this
 | 
						|
            attribute, adjusted for any aliasing in progress.
 | 
						|
 | 
						|
            .. versionadded:: 1.3.17
 | 
						|
 | 
						|
            """
 | 
						|
            if self.adapter:
 | 
						|
                return [
 | 
						|
                    self.adapter(col, self.prop.key)
 | 
						|
                    for col in self.prop.columns
 | 
						|
                ]
 | 
						|
            else:
 | 
						|
                return [
 | 
						|
                    self._orm_annotate_column(col) for col in self.prop.columns
 | 
						|
                ]
 | 
						|
 | 
						|
        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
 |