123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- # 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
|