first add files
This commit is contained in:
430
lib/sqlalchemy/orm/properties.py
Normal file
430
lib/sqlalchemy/orm/properties.py
Normal file
@@ -0,0 +1,430 @@
|
||||
# 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 .. 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__ = (
|
||||
"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.columns = [
|
||||
coercions.expect(roles.LabeledColumnExprRole, 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
|
||||
Reference in New Issue
Block a user