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.
		
		
		
		
		
			
		
			
				
					
					
						
							2331 lines
						
					
					
						
							75 KiB
						
					
					
				
			
		
		
	
	
							2331 lines
						
					
					
						
							75 KiB
						
					
					
				# orm/attributes.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
 | 
						|
 | 
						|
"""Defines instrumentation for class attributes and their interaction
 | 
						|
with instances.
 | 
						|
 | 
						|
This module is usually not directly visible to user applications, but
 | 
						|
defines a large part of the ORM's interactivity.
 | 
						|
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
import operator
 | 
						|
 | 
						|
from . import collections
 | 
						|
from . import exc as orm_exc
 | 
						|
from . import interfaces
 | 
						|
from .base import ATTR_EMPTY
 | 
						|
from .base import ATTR_WAS_SET
 | 
						|
from .base import CALLABLES_OK
 | 
						|
from .base import DEFERRED_HISTORY_LOAD
 | 
						|
from .base import INIT_OK
 | 
						|
from .base import instance_dict
 | 
						|
from .base import instance_state
 | 
						|
from .base import instance_str
 | 
						|
from .base import LOAD_AGAINST_COMMITTED
 | 
						|
from .base import manager_of_class
 | 
						|
from .base import NEVER_SET  # noqa
 | 
						|
from .base import NO_AUTOFLUSH
 | 
						|
from .base import NO_CHANGE  # noqa
 | 
						|
from .base import NO_RAISE
 | 
						|
from .base import NO_VALUE
 | 
						|
from .base import NON_PERSISTENT_OK  # noqa
 | 
						|
from .base import PASSIVE_CLASS_MISMATCH  # noqa
 | 
						|
from .base import PASSIVE_NO_FETCH
 | 
						|
from .base import PASSIVE_NO_FETCH_RELATED  # noqa
 | 
						|
from .base import PASSIVE_NO_INITIALIZE
 | 
						|
from .base import PASSIVE_NO_RESULT
 | 
						|
from .base import PASSIVE_OFF
 | 
						|
from .base import PASSIVE_ONLY_PERSISTENT
 | 
						|
from .base import PASSIVE_RETURN_NO_VALUE
 | 
						|
from .base import RELATED_OBJECT_OK  # noqa
 | 
						|
from .base import SQL_OK  # noqa
 | 
						|
from .base import state_str
 | 
						|
from .. import event
 | 
						|
from .. import exc
 | 
						|
from .. import inspection
 | 
						|
from .. import util
 | 
						|
from ..sql import base as sql_base
 | 
						|
from ..sql import roles
 | 
						|
from ..sql import traversals
 | 
						|
from ..sql import visitors
 | 
						|
 | 
						|
 | 
						|
class NoKey(str):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
NO_KEY = NoKey("no name")
 | 
						|
 | 
						|
 | 
						|
@inspection._self_inspects
 | 
						|
class QueryableAttribute(
 | 
						|
    interfaces._MappedAttribute,
 | 
						|
    interfaces.InspectionAttr,
 | 
						|
    interfaces.PropComparator,
 | 
						|
    traversals.HasCopyInternals,
 | 
						|
    roles.JoinTargetRole,
 | 
						|
    roles.OnClauseRole,
 | 
						|
    sql_base.Immutable,
 | 
						|
    sql_base.MemoizedHasCacheKey,
 | 
						|
):
 | 
						|
    """Base class for :term:`descriptor` objects that intercept
 | 
						|
    attribute events on behalf of a :class:`.MapperProperty`
 | 
						|
    object.  The actual :class:`.MapperProperty` is accessible
 | 
						|
    via the :attr:`.QueryableAttribute.property`
 | 
						|
    attribute.
 | 
						|
 | 
						|
 | 
						|
    .. seealso::
 | 
						|
 | 
						|
        :class:`.InstrumentedAttribute`
 | 
						|
 | 
						|
        :class:`.MapperProperty`
 | 
						|
 | 
						|
        :attr:`_orm.Mapper.all_orm_descriptors`
 | 
						|
 | 
						|
        :attr:`_orm.Mapper.attrs`
 | 
						|
    """
 | 
						|
 | 
						|
    is_attribute = True
 | 
						|
 | 
						|
    # PropComparator has a __visit_name__ to participate within
 | 
						|
    # traversals.   Disambiguate the attribute vs. a comparator.
 | 
						|
    __visit_name__ = "orm_instrumented_attribute"
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        class_,
 | 
						|
        key,
 | 
						|
        parententity,
 | 
						|
        impl=None,
 | 
						|
        comparator=None,
 | 
						|
        of_type=None,
 | 
						|
        extra_criteria=(),
 | 
						|
    ):
 | 
						|
        self.class_ = class_
 | 
						|
        self.key = key
 | 
						|
        self._parententity = parententity
 | 
						|
        self.impl = impl
 | 
						|
        self.comparator = comparator
 | 
						|
        self._of_type = of_type
 | 
						|
        self._extra_criteria = extra_criteria
 | 
						|
 | 
						|
        manager = manager_of_class(class_)
 | 
						|
        # manager is None in the case of AliasedClass
 | 
						|
        if manager:
 | 
						|
            # propagate existing event listeners from
 | 
						|
            # immediate superclass
 | 
						|
            for base in manager._bases:
 | 
						|
                if key in base:
 | 
						|
                    self.dispatch._update(base[key].dispatch)
 | 
						|
                    if base[key].dispatch._active_history:
 | 
						|
                        self.dispatch._active_history = True
 | 
						|
 | 
						|
    _cache_key_traversal = [
 | 
						|
        ("key", visitors.ExtendedInternalTraversal.dp_string),
 | 
						|
        ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
 | 
						|
        ("_of_type", visitors.ExtendedInternalTraversal.dp_multi),
 | 
						|
        ("_extra_criteria", visitors.InternalTraversal.dp_clauseelement_list),
 | 
						|
    ]
 | 
						|
 | 
						|
    def __reduce__(self):
 | 
						|
        # this method is only used in terms of the
 | 
						|
        # sqlalchemy.ext.serializer extension
 | 
						|
        return (
 | 
						|
            _queryable_attribute_unreduce,
 | 
						|
            (
 | 
						|
                self.key,
 | 
						|
                self._parententity.mapper.class_,
 | 
						|
                self._parententity,
 | 
						|
                self._parententity.entity,
 | 
						|
            ),
 | 
						|
        )
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def _supports_population(self):
 | 
						|
        return self.impl.supports_population
 | 
						|
 | 
						|
    @property
 | 
						|
    def _impl_uses_objects(self):
 | 
						|
        return self.impl.uses_objects
 | 
						|
 | 
						|
    def get_history(self, instance, passive=PASSIVE_OFF):
 | 
						|
        return self.impl.get_history(
 | 
						|
            instance_state(instance), instance_dict(instance), passive
 | 
						|
        )
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def info(self):
 | 
						|
        """Return the 'info' dictionary for the underlying SQL element.
 | 
						|
 | 
						|
        The behavior here is as follows:
 | 
						|
 | 
						|
        * If the attribute is a column-mapped property, i.e.
 | 
						|
          :class:`.ColumnProperty`, which is mapped directly
 | 
						|
          to a schema-level :class:`_schema.Column` object, this attribute
 | 
						|
          will return the :attr:`.SchemaItem.info` dictionary associated
 | 
						|
          with the core-level :class:`_schema.Column` object.
 | 
						|
 | 
						|
        * If the attribute is a :class:`.ColumnProperty` but is mapped to
 | 
						|
          any other kind of SQL expression other than a
 | 
						|
          :class:`_schema.Column`,
 | 
						|
          the attribute will refer to the :attr:`.MapperProperty.info`
 | 
						|
          dictionary associated directly with the :class:`.ColumnProperty`,
 | 
						|
          assuming the SQL expression itself does not have its own ``.info``
 | 
						|
          attribute (which should be the case, unless a user-defined SQL
 | 
						|
          construct has defined one).
 | 
						|
 | 
						|
        * If the attribute refers to any other kind of
 | 
						|
          :class:`.MapperProperty`, including :class:`.RelationshipProperty`,
 | 
						|
          the attribute will refer to the :attr:`.MapperProperty.info`
 | 
						|
          dictionary associated with that :class:`.MapperProperty`.
 | 
						|
 | 
						|
        * To access the :attr:`.MapperProperty.info` dictionary of the
 | 
						|
          :class:`.MapperProperty` unconditionally, including for a
 | 
						|
          :class:`.ColumnProperty` that's associated directly with a
 | 
						|
          :class:`_schema.Column`, the attribute can be referred to using
 | 
						|
          :attr:`.QueryableAttribute.property` attribute, as
 | 
						|
          ``MyClass.someattribute.property.info``.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :attr:`.SchemaItem.info`
 | 
						|
 | 
						|
            :attr:`.MapperProperty.info`
 | 
						|
 | 
						|
        """
 | 
						|
        return self.comparator.info
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def parent(self):
 | 
						|
        """Return an inspection instance representing the parent.
 | 
						|
 | 
						|
        This will be either an instance of :class:`_orm.Mapper`
 | 
						|
        or :class:`.AliasedInsp`, depending upon the nature
 | 
						|
        of the parent entity which this attribute is associated
 | 
						|
        with.
 | 
						|
 | 
						|
        """
 | 
						|
        return inspection.inspect(self._parententity)
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def expression(self):
 | 
						|
        """The SQL expression object represented by this
 | 
						|
        :class:`.QueryableAttribute`.
 | 
						|
 | 
						|
        This will typically be an instance of a :class:`_sql.ColumnElement`
 | 
						|
        subclass representing a column expression.
 | 
						|
 | 
						|
        """
 | 
						|
        if self.key is NO_KEY:
 | 
						|
            annotations = {"entity_namespace": self._entity_namespace}
 | 
						|
        else:
 | 
						|
            annotations = {
 | 
						|
                "proxy_key": self.key,
 | 
						|
                "proxy_owner": self._parententity,
 | 
						|
                "entity_namespace": self._entity_namespace,
 | 
						|
            }
 | 
						|
 | 
						|
        ce = self.comparator.__clause_element__()
 | 
						|
        try:
 | 
						|
            anno = ce._annotate
 | 
						|
        except AttributeError as ae:
 | 
						|
            util.raise_(
 | 
						|
                exc.InvalidRequestError(
 | 
						|
                    'When interpreting attribute "%s" as a SQL expression, '
 | 
						|
                    "expected __clause_element__() to return "
 | 
						|
                    "a ClauseElement object, got: %r" % (self, ce)
 | 
						|
                ),
 | 
						|
                from_=ae,
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            return anno(annotations)
 | 
						|
 | 
						|
    @property
 | 
						|
    def _entity_namespace(self):
 | 
						|
        return self._parententity
 | 
						|
 | 
						|
    @property
 | 
						|
    def _annotations(self):
 | 
						|
        return self.__clause_element__()._annotations
 | 
						|
 | 
						|
    def __clause_element__(self):
 | 
						|
        return self.expression
 | 
						|
 | 
						|
    @property
 | 
						|
    def _from_objects(self):
 | 
						|
        return self.expression._from_objects
 | 
						|
 | 
						|
    def _bulk_update_tuples(self, value):
 | 
						|
        """Return setter tuples for a bulk UPDATE."""
 | 
						|
 | 
						|
        return self.comparator._bulk_update_tuples(value)
 | 
						|
 | 
						|
    def adapt_to_entity(self, adapt_to_entity):
 | 
						|
        assert not self._of_type
 | 
						|
        return self.__class__(
 | 
						|
            adapt_to_entity.entity,
 | 
						|
            self.key,
 | 
						|
            impl=self.impl,
 | 
						|
            comparator=self.comparator.adapt_to_entity(adapt_to_entity),
 | 
						|
            parententity=adapt_to_entity,
 | 
						|
        )
 | 
						|
 | 
						|
    def of_type(self, entity):
 | 
						|
        return QueryableAttribute(
 | 
						|
            self.class_,
 | 
						|
            self.key,
 | 
						|
            self._parententity,
 | 
						|
            impl=self.impl,
 | 
						|
            comparator=self.comparator.of_type(entity),
 | 
						|
            of_type=inspection.inspect(entity),
 | 
						|
            extra_criteria=self._extra_criteria,
 | 
						|
        )
 | 
						|
 | 
						|
    def and_(self, *other):
 | 
						|
        return QueryableAttribute(
 | 
						|
            self.class_,
 | 
						|
            self.key,
 | 
						|
            self._parententity,
 | 
						|
            impl=self.impl,
 | 
						|
            comparator=self.comparator.and_(*other),
 | 
						|
            of_type=self._of_type,
 | 
						|
            extra_criteria=self._extra_criteria + other,
 | 
						|
        )
 | 
						|
 | 
						|
    def _clone(self, **kw):
 | 
						|
        return QueryableAttribute(
 | 
						|
            self.class_,
 | 
						|
            self.key,
 | 
						|
            self._parententity,
 | 
						|
            impl=self.impl,
 | 
						|
            comparator=self.comparator,
 | 
						|
            of_type=self._of_type,
 | 
						|
            extra_criteria=self._extra_criteria,
 | 
						|
        )
 | 
						|
 | 
						|
    def label(self, name):
 | 
						|
        return self.__clause_element__().label(name)
 | 
						|
 | 
						|
    def operate(self, op, *other, **kwargs):
 | 
						|
        return op(self.comparator, *other, **kwargs)
 | 
						|
 | 
						|
    def reverse_operate(self, op, other, **kwargs):
 | 
						|
        return op(other, self.comparator, **kwargs)
 | 
						|
 | 
						|
    def hasparent(self, state, optimistic=False):
 | 
						|
        return self.impl.hasparent(state, optimistic=optimistic) is not False
 | 
						|
 | 
						|
    def __getattr__(self, key):
 | 
						|
        try:
 | 
						|
            return getattr(self.comparator, key)
 | 
						|
        except AttributeError as err:
 | 
						|
            util.raise_(
 | 
						|
                AttributeError(
 | 
						|
                    "Neither %r object nor %r object associated with %s "
 | 
						|
                    "has an attribute %r"
 | 
						|
                    % (
 | 
						|
                        type(self).__name__,
 | 
						|
                        type(self.comparator).__name__,
 | 
						|
                        self,
 | 
						|
                        key,
 | 
						|
                    )
 | 
						|
                ),
 | 
						|
                replace_context=err,
 | 
						|
            )
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return "%s.%s" % (self.class_.__name__, self.key)
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def property(self):
 | 
						|
        """Return the :class:`.MapperProperty` associated with this
 | 
						|
        :class:`.QueryableAttribute`.
 | 
						|
 | 
						|
 | 
						|
        Return values here will commonly be instances of
 | 
						|
        :class:`.ColumnProperty` or :class:`.RelationshipProperty`.
 | 
						|
 | 
						|
 | 
						|
        """
 | 
						|
        return self.comparator.property
 | 
						|
 | 
						|
 | 
						|
def _queryable_attribute_unreduce(key, mapped_class, parententity, entity):
 | 
						|
    # this method is only used in terms of the
 | 
						|
    # sqlalchemy.ext.serializer extension
 | 
						|
    if parententity.is_aliased_class:
 | 
						|
        return entity._get_from_serialized(key, mapped_class, parententity)
 | 
						|
    else:
 | 
						|
        return getattr(entity, key)
 | 
						|
 | 
						|
 | 
						|
if util.py3k:
 | 
						|
    from typing import TypeVar, Generic
 | 
						|
 | 
						|
    _T = TypeVar("_T")
 | 
						|
    _Generic_T = Generic[_T]
 | 
						|
else:
 | 
						|
    _Generic_T = type("_Generic_T", (), {})
 | 
						|
 | 
						|
 | 
						|
class Mapped(QueryableAttribute, _Generic_T):
 | 
						|
    """Represent an ORM mapped :term:`descriptor` attribute for typing purposes.
 | 
						|
 | 
						|
    This class represents the complete descriptor interface for any class
 | 
						|
    attribute that will have been :term:`instrumented` by the ORM
 | 
						|
    :class:`_orm.Mapper` class. When used with typing stubs, it is the final
 | 
						|
    type that would be used by a type checker such as mypy to provide the full
 | 
						|
    behavioral contract for the attribute.
 | 
						|
 | 
						|
    .. tip::
 | 
						|
 | 
						|
        The :class:`_orm.Mapped` class represents attributes that are handled
 | 
						|
        directly by the :class:`_orm.Mapper` class. It does not include other
 | 
						|
        Python descriptor classes that are provided as extensions, including
 | 
						|
        :ref:`hybrids_toplevel` and the :ref:`associationproxy_toplevel`.
 | 
						|
        While these systems still make use of ORM-specific superclasses
 | 
						|
        and structures, they are not :term:`instrumented` by the
 | 
						|
        :class:`_orm.Mapper` and instead provide their own functionality
 | 
						|
        when they are accessed on a class.
 | 
						|
 | 
						|
    When using the :ref:`SQLAlchemy Mypy plugin <mypy_toplevel>`, the
 | 
						|
    :class:`_orm.Mapped` construct is used in typing annotations to indicate to
 | 
						|
    the plugin those attributes that are expected to be mapped; the plugin also
 | 
						|
    applies :class:`_orm.Mapped` as an annotation automatically when it scans
 | 
						|
    through declarative mappings in :ref:`orm_declarative_table` style. For
 | 
						|
    more indirect mapping styles such as
 | 
						|
    :ref:`imperative table <orm_imperative_table_configuration>` it is
 | 
						|
    typically applied explicitly to class level attributes that expect
 | 
						|
    to be mapped based on a given :class:`_schema.Table` configuration.
 | 
						|
 | 
						|
    :class:`_orm.Mapped` is defined in the
 | 
						|
    `sqlalchemy2-stubs <https://pypi.org/project/sqlalchemy2-stubs>`_ project
 | 
						|
    as a :pep:`484` generic class which may subscribe to any arbitrary Python
 | 
						|
    type, which represents the Python type handled by the attribute::
 | 
						|
 | 
						|
        class MyMappedClass(Base):
 | 
						|
            __table_ = Table(
 | 
						|
                "some_table", Base.metadata,
 | 
						|
                Column("id", Integer, primary_key=True),
 | 
						|
                Column("data", String(50)),
 | 
						|
                Column("created_at", DateTime)
 | 
						|
            )
 | 
						|
 | 
						|
            id : Mapped[int]
 | 
						|
            data: Mapped[str]
 | 
						|
            created_at: Mapped[datetime]
 | 
						|
 | 
						|
    For complete background on how to use :class:`_orm.Mapped` with
 | 
						|
    pep-484 tools like Mypy, see the link below for background on SQLAlchemy's
 | 
						|
    Mypy plugin.
 | 
						|
 | 
						|
    .. versionadded:: 1.4
 | 
						|
 | 
						|
    .. seealso::
 | 
						|
 | 
						|
        :ref:`mypy_toplevel` - complete background on Mypy integration
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __get__(self, instance, owner):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
    def __set__(self, instance, value):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
    def __delete__(self, instance):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
 | 
						|
class InstrumentedAttribute(Mapped):
 | 
						|
    """Class bound instrumented attribute which adds basic
 | 
						|
    :term:`descriptor` methods.
 | 
						|
 | 
						|
    See :class:`.QueryableAttribute` for a description of most features.
 | 
						|
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    inherit_cache = True
 | 
						|
 | 
						|
    def __set__(self, instance, value):
 | 
						|
        self.impl.set(
 | 
						|
            instance_state(instance), instance_dict(instance), value, None
 | 
						|
        )
 | 
						|
 | 
						|
    def __delete__(self, instance):
 | 
						|
        self.impl.delete(instance_state(instance), instance_dict(instance))
 | 
						|
 | 
						|
    def __get__(self, instance, owner):
 | 
						|
        if instance is None:
 | 
						|
            return self
 | 
						|
 | 
						|
        dict_ = instance_dict(instance)
 | 
						|
        if self._supports_population and self.key in dict_:
 | 
						|
            return dict_[self.key]
 | 
						|
        else:
 | 
						|
            try:
 | 
						|
                state = instance_state(instance)
 | 
						|
            except AttributeError as err:
 | 
						|
                util.raise_(
 | 
						|
                    orm_exc.UnmappedInstanceError(instance),
 | 
						|
                    replace_context=err,
 | 
						|
                )
 | 
						|
            return self.impl.get(state, dict_)
 | 
						|
 | 
						|
 | 
						|
HasEntityNamespace = util.namedtuple(
 | 
						|
    "HasEntityNamespace", ["entity_namespace"]
 | 
						|
)
 | 
						|
HasEntityNamespace.is_mapper = HasEntityNamespace.is_aliased_class = False
 | 
						|
 | 
						|
 | 
						|
def create_proxied_attribute(descriptor):
 | 
						|
    """Create an QueryableAttribute / user descriptor hybrid.
 | 
						|
 | 
						|
    Returns a new QueryableAttribute type that delegates descriptor
 | 
						|
    behavior and getattr() to the given descriptor.
 | 
						|
    """
 | 
						|
 | 
						|
    # TODO: can move this to descriptor_props if the need for this
 | 
						|
    # function is removed from ext/hybrid.py
 | 
						|
 | 
						|
    class Proxy(QueryableAttribute):
 | 
						|
        """Presents the :class:`.QueryableAttribute` interface as a
 | 
						|
        proxy on top of a Python descriptor / :class:`.PropComparator`
 | 
						|
        combination.
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        _extra_criteria = ()
 | 
						|
 | 
						|
        def __init__(
 | 
						|
            self,
 | 
						|
            class_,
 | 
						|
            key,
 | 
						|
            descriptor,
 | 
						|
            comparator,
 | 
						|
            adapt_to_entity=None,
 | 
						|
            doc=None,
 | 
						|
            original_property=None,
 | 
						|
        ):
 | 
						|
            self.class_ = class_
 | 
						|
            self.key = key
 | 
						|
            self.descriptor = descriptor
 | 
						|
            self.original_property = original_property
 | 
						|
            self._comparator = comparator
 | 
						|
            self._adapt_to_entity = adapt_to_entity
 | 
						|
            self.__doc__ = doc
 | 
						|
 | 
						|
        _is_internal_proxy = True
 | 
						|
 | 
						|
        _cache_key_traversal = [
 | 
						|
            ("key", visitors.ExtendedInternalTraversal.dp_string),
 | 
						|
            ("_parententity", visitors.ExtendedInternalTraversal.dp_multi),
 | 
						|
        ]
 | 
						|
 | 
						|
        @property
 | 
						|
        def _impl_uses_objects(self):
 | 
						|
            return (
 | 
						|
                self.original_property is not None
 | 
						|
                and getattr(self.class_, self.key).impl.uses_objects
 | 
						|
            )
 | 
						|
 | 
						|
        @property
 | 
						|
        def _parententity(self):
 | 
						|
            return inspection.inspect(self.class_, raiseerr=False)
 | 
						|
 | 
						|
        @property
 | 
						|
        def _entity_namespace(self):
 | 
						|
            if hasattr(self._comparator, "_parententity"):
 | 
						|
                return self._comparator._parententity
 | 
						|
            else:
 | 
						|
                # used by hybrid attributes which try to remain
 | 
						|
                # agnostic of any ORM concepts like mappers
 | 
						|
                return HasEntityNamespace(self.class_)
 | 
						|
 | 
						|
        @property
 | 
						|
        def property(self):
 | 
						|
            return self.comparator.property
 | 
						|
 | 
						|
        @util.memoized_property
 | 
						|
        def comparator(self):
 | 
						|
            if callable(self._comparator):
 | 
						|
                self._comparator = self._comparator()
 | 
						|
            if self._adapt_to_entity:
 | 
						|
                self._comparator = self._comparator.adapt_to_entity(
 | 
						|
                    self._adapt_to_entity
 | 
						|
                )
 | 
						|
            return self._comparator
 | 
						|
 | 
						|
        def adapt_to_entity(self, adapt_to_entity):
 | 
						|
            return self.__class__(
 | 
						|
                adapt_to_entity.entity,
 | 
						|
                self.key,
 | 
						|
                self.descriptor,
 | 
						|
                self._comparator,
 | 
						|
                adapt_to_entity,
 | 
						|
            )
 | 
						|
 | 
						|
        def _clone(self, **kw):
 | 
						|
            return self.__class__(
 | 
						|
                self.class_,
 | 
						|
                self.key,
 | 
						|
                self.descriptor,
 | 
						|
                self._comparator,
 | 
						|
                adapt_to_entity=self._adapt_to_entity,
 | 
						|
                original_property=self.original_property,
 | 
						|
            )
 | 
						|
 | 
						|
        def __get__(self, instance, owner):
 | 
						|
            retval = self.descriptor.__get__(instance, owner)
 | 
						|
            # detect if this is a plain Python @property, which just returns
 | 
						|
            # itself for class level access.  If so, then return us.
 | 
						|
            # Otherwise, return the object returned by the descriptor.
 | 
						|
            if retval is self.descriptor and instance is None:
 | 
						|
                return self
 | 
						|
            else:
 | 
						|
                return retval
 | 
						|
 | 
						|
        def __str__(self):
 | 
						|
            return "%s.%s" % (self.class_.__name__, self.key)
 | 
						|
 | 
						|
        def __getattr__(self, attribute):
 | 
						|
            """Delegate __getattr__ to the original descriptor and/or
 | 
						|
            comparator."""
 | 
						|
            try:
 | 
						|
                return getattr(descriptor, attribute)
 | 
						|
            except AttributeError as err:
 | 
						|
                if attribute == "comparator":
 | 
						|
                    util.raise_(
 | 
						|
                        AttributeError("comparator"), replace_context=err
 | 
						|
                    )
 | 
						|
                try:
 | 
						|
                    # comparator itself might be unreachable
 | 
						|
                    comparator = self.comparator
 | 
						|
                except AttributeError as err2:
 | 
						|
                    util.raise_(
 | 
						|
                        AttributeError(
 | 
						|
                            "Neither %r object nor unconfigured comparator "
 | 
						|
                            "object associated with %s has an attribute %r"
 | 
						|
                            % (type(descriptor).__name__, self, attribute)
 | 
						|
                        ),
 | 
						|
                        replace_context=err2,
 | 
						|
                    )
 | 
						|
                else:
 | 
						|
                    try:
 | 
						|
                        return getattr(comparator, attribute)
 | 
						|
                    except AttributeError as err3:
 | 
						|
                        util.raise_(
 | 
						|
                            AttributeError(
 | 
						|
                                "Neither %r object nor %r object "
 | 
						|
                                "associated with %s has an attribute %r"
 | 
						|
                                % (
 | 
						|
                                    type(descriptor).__name__,
 | 
						|
                                    type(comparator).__name__,
 | 
						|
                                    self,
 | 
						|
                                    attribute,
 | 
						|
                                )
 | 
						|
                            ),
 | 
						|
                            replace_context=err3,
 | 
						|
                        )
 | 
						|
 | 
						|
    Proxy.__name__ = type(descriptor).__name__ + "Proxy"
 | 
						|
 | 
						|
    util.monkeypatch_proxied_specials(
 | 
						|
        Proxy, type(descriptor), name="descriptor", from_instance=descriptor
 | 
						|
    )
 | 
						|
    return Proxy
 | 
						|
 | 
						|
 | 
						|
OP_REMOVE = util.symbol("REMOVE")
 | 
						|
OP_APPEND = util.symbol("APPEND")
 | 
						|
OP_REPLACE = util.symbol("REPLACE")
 | 
						|
OP_BULK_REPLACE = util.symbol("BULK_REPLACE")
 | 
						|
OP_MODIFIED = util.symbol("MODIFIED")
 | 
						|
 | 
						|
 | 
						|
class AttributeEvent(object):
 | 
						|
    """A token propagated throughout the course of a chain of attribute
 | 
						|
    events.
 | 
						|
 | 
						|
    Serves as an indicator of the source of the event and also provides
 | 
						|
    a means of controlling propagation across a chain of attribute
 | 
						|
    operations.
 | 
						|
 | 
						|
    The :class:`.Event` object is sent as the ``initiator`` argument
 | 
						|
    when dealing with events such as :meth:`.AttributeEvents.append`,
 | 
						|
    :meth:`.AttributeEvents.set`,
 | 
						|
    and :meth:`.AttributeEvents.remove`.
 | 
						|
 | 
						|
    The :class:`.Event` object is currently interpreted by the backref
 | 
						|
    event handlers, and is used to control the propagation of operations
 | 
						|
    across two mutually-dependent attributes.
 | 
						|
 | 
						|
    .. versionadded:: 0.9.0
 | 
						|
 | 
						|
    :attribute impl: The :class:`.AttributeImpl` which is the current event
 | 
						|
     initiator.
 | 
						|
 | 
						|
    :attribute op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE`,
 | 
						|
     :attr:`.OP_REPLACE`, or :attr:`.OP_BULK_REPLACE`, indicating the
 | 
						|
     source operation.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    __slots__ = "impl", "op", "parent_token"
 | 
						|
 | 
						|
    def __init__(self, attribute_impl, op):
 | 
						|
        self.impl = attribute_impl
 | 
						|
        self.op = op
 | 
						|
        self.parent_token = self.impl.parent_token
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        return (
 | 
						|
            isinstance(other, AttributeEvent)
 | 
						|
            and other.impl is self.impl
 | 
						|
            and other.op == self.op
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def key(self):
 | 
						|
        return self.impl.key
 | 
						|
 | 
						|
    def hasparent(self, state):
 | 
						|
        return self.impl.hasparent(state)
 | 
						|
 | 
						|
 | 
						|
Event = AttributeEvent
 | 
						|
 | 
						|
 | 
						|
class AttributeImpl(object):
 | 
						|
    """internal implementation for instrumented attributes."""
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        class_,
 | 
						|
        key,
 | 
						|
        callable_,
 | 
						|
        dispatch,
 | 
						|
        trackparent=False,
 | 
						|
        compare_function=None,
 | 
						|
        active_history=False,
 | 
						|
        parent_token=None,
 | 
						|
        load_on_unexpire=True,
 | 
						|
        send_modified_events=True,
 | 
						|
        accepts_scalar_loader=None,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        r"""Construct an AttributeImpl.
 | 
						|
 | 
						|
        :param \class_: associated class
 | 
						|
 | 
						|
        :param key: string name of the attribute
 | 
						|
 | 
						|
        :param \callable_:
 | 
						|
          optional function which generates a callable based on a parent
 | 
						|
          instance, which produces the "default" values for a scalar or
 | 
						|
          collection attribute when it's first accessed, if not present
 | 
						|
          already.
 | 
						|
 | 
						|
        :param trackparent:
 | 
						|
          if True, attempt to track if an instance has a parent attached
 | 
						|
          to it via this attribute.
 | 
						|
 | 
						|
        :param compare_function:
 | 
						|
          a function that compares two values which are normally
 | 
						|
          assignable to this attribute.
 | 
						|
 | 
						|
        :param active_history:
 | 
						|
          indicates that get_history() should always return the "old" value,
 | 
						|
          even if it means executing a lazy callable upon attribute change.
 | 
						|
 | 
						|
        :param parent_token:
 | 
						|
          Usually references the MapperProperty, used as a key for
 | 
						|
          the hasparent() function to identify an "owning" attribute.
 | 
						|
          Allows multiple AttributeImpls to all match a single
 | 
						|
          owner attribute.
 | 
						|
 | 
						|
        :param load_on_unexpire:
 | 
						|
          if False, don't include this attribute in a load-on-expired
 | 
						|
          operation, i.e. the "expired_attribute_loader" process.
 | 
						|
          The attribute can still be in the "expired" list and be
 | 
						|
          considered to be "expired".   Previously, this flag was called
 | 
						|
          "expire_missing" and is only used by a deferred column
 | 
						|
          attribute.
 | 
						|
 | 
						|
        :param send_modified_events:
 | 
						|
          if False, the InstanceState._modified_event method will have no
 | 
						|
          effect; this means the attribute will never show up as changed in a
 | 
						|
          history entry.
 | 
						|
 | 
						|
        """
 | 
						|
        self.class_ = class_
 | 
						|
        self.key = key
 | 
						|
        self.callable_ = callable_
 | 
						|
        self.dispatch = dispatch
 | 
						|
        self.trackparent = trackparent
 | 
						|
        self.parent_token = parent_token or self
 | 
						|
        self.send_modified_events = send_modified_events
 | 
						|
        if compare_function is None:
 | 
						|
            self.is_equal = operator.eq
 | 
						|
        else:
 | 
						|
            self.is_equal = compare_function
 | 
						|
 | 
						|
        if accepts_scalar_loader is not None:
 | 
						|
            self.accepts_scalar_loader = accepts_scalar_loader
 | 
						|
        else:
 | 
						|
            self.accepts_scalar_loader = self.default_accepts_scalar_loader
 | 
						|
 | 
						|
        _deferred_history = kwargs.pop("_deferred_history", False)
 | 
						|
        self._deferred_history = _deferred_history
 | 
						|
 | 
						|
        if active_history:
 | 
						|
            self.dispatch._active_history = True
 | 
						|
 | 
						|
        self.load_on_unexpire = load_on_unexpire
 | 
						|
        self._modified_token = Event(self, OP_MODIFIED)
 | 
						|
 | 
						|
    __slots__ = (
 | 
						|
        "class_",
 | 
						|
        "key",
 | 
						|
        "callable_",
 | 
						|
        "dispatch",
 | 
						|
        "trackparent",
 | 
						|
        "parent_token",
 | 
						|
        "send_modified_events",
 | 
						|
        "is_equal",
 | 
						|
        "load_on_unexpire",
 | 
						|
        "_modified_token",
 | 
						|
        "accepts_scalar_loader",
 | 
						|
        "_deferred_history",
 | 
						|
    )
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return "%s.%s" % (self.class_.__name__, self.key)
 | 
						|
 | 
						|
    def _get_active_history(self):
 | 
						|
        """Backwards compat for impl.active_history"""
 | 
						|
 | 
						|
        return self.dispatch._active_history
 | 
						|
 | 
						|
    def _set_active_history(self, value):
 | 
						|
        self.dispatch._active_history = value
 | 
						|
 | 
						|
    active_history = property(_get_active_history, _set_active_history)
 | 
						|
 | 
						|
    def hasparent(self, state, optimistic=False):
 | 
						|
        """Return the boolean value of a `hasparent` flag attached to
 | 
						|
        the given state.
 | 
						|
 | 
						|
        The `optimistic` flag determines what the default return value
 | 
						|
        should be if no `hasparent` flag can be located.
 | 
						|
 | 
						|
        As this function is used to determine if an instance is an
 | 
						|
        *orphan*, instances that were loaded from storage should be
 | 
						|
        assumed to not be orphans, until a True/False value for this
 | 
						|
        flag is set.
 | 
						|
 | 
						|
        An instance attribute that is loaded by a callable function
 | 
						|
        will also not have a `hasparent` flag.
 | 
						|
 | 
						|
        """
 | 
						|
        msg = "This AttributeImpl is not configured to track parents."
 | 
						|
        assert self.trackparent, msg
 | 
						|
 | 
						|
        return (
 | 
						|
            state.parents.get(id(self.parent_token), optimistic) is not False
 | 
						|
        )
 | 
						|
 | 
						|
    def sethasparent(self, state, parent_state, value):
 | 
						|
        """Set a boolean flag on the given item corresponding to
 | 
						|
        whether or not it is attached to a parent object via the
 | 
						|
        attribute represented by this ``InstrumentedAttribute``.
 | 
						|
 | 
						|
        """
 | 
						|
        msg = "This AttributeImpl is not configured to track parents."
 | 
						|
        assert self.trackparent, msg
 | 
						|
 | 
						|
        id_ = id(self.parent_token)
 | 
						|
        if value:
 | 
						|
            state.parents[id_] = parent_state
 | 
						|
        else:
 | 
						|
            if id_ in state.parents:
 | 
						|
                last_parent = state.parents[id_]
 | 
						|
 | 
						|
                if (
 | 
						|
                    last_parent is not False
 | 
						|
                    and last_parent.key != parent_state.key
 | 
						|
                ):
 | 
						|
 | 
						|
                    if last_parent.obj() is None:
 | 
						|
                        raise orm_exc.StaleDataError(
 | 
						|
                            "Removing state %s from parent "
 | 
						|
                            "state %s along attribute '%s', "
 | 
						|
                            "but the parent record "
 | 
						|
                            "has gone stale, can't be sure this "
 | 
						|
                            "is the most recent parent."
 | 
						|
                            % (
 | 
						|
                                state_str(state),
 | 
						|
                                state_str(parent_state),
 | 
						|
                                self.key,
 | 
						|
                            )
 | 
						|
                        )
 | 
						|
 | 
						|
                    return
 | 
						|
 | 
						|
            state.parents[id_] = False
 | 
						|
 | 
						|
    def get_history(self, state, dict_, passive=PASSIVE_OFF):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
    def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
 | 
						|
        """Return a list of tuples of (state, obj)
 | 
						|
        for all objects in this attribute's current state
 | 
						|
        + history.
 | 
						|
 | 
						|
        Only applies to object-based attributes.
 | 
						|
 | 
						|
        This is an inlining of existing functionality
 | 
						|
        which roughly corresponds to:
 | 
						|
 | 
						|
            get_state_history(
 | 
						|
                        state,
 | 
						|
                        key,
 | 
						|
                        passive=PASSIVE_NO_INITIALIZE).sum()
 | 
						|
 | 
						|
        """
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
    def _default_value(self, state, dict_):
 | 
						|
        """Produce an empty value for an uninitialized scalar attribute."""
 | 
						|
 | 
						|
        assert self.key not in dict_, (
 | 
						|
            "_default_value should only be invoked for an "
 | 
						|
            "uninitialized or expired attribute"
 | 
						|
        )
 | 
						|
 | 
						|
        value = None
 | 
						|
        for fn in self.dispatch.init_scalar:
 | 
						|
            ret = fn(state, value, dict_)
 | 
						|
            if ret is not ATTR_EMPTY:
 | 
						|
                value = ret
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
    def get(self, state, dict_, passive=PASSIVE_OFF):
 | 
						|
        """Retrieve a value from the given object.
 | 
						|
        If a callable is assembled on this object's attribute, and
 | 
						|
        passive is False, the callable will be executed and the
 | 
						|
        resulting value will be set as the new value for this attribute.
 | 
						|
        """
 | 
						|
        if self.key in dict_:
 | 
						|
            return dict_[self.key]
 | 
						|
        else:
 | 
						|
            # if history present, don't load
 | 
						|
            key = self.key
 | 
						|
            if (
 | 
						|
                key not in state.committed_state
 | 
						|
                or state.committed_state[key] is NO_VALUE
 | 
						|
            ):
 | 
						|
                if not passive & CALLABLES_OK:
 | 
						|
                    return PASSIVE_NO_RESULT
 | 
						|
 | 
						|
                value = self._fire_loader_callables(state, key, passive)
 | 
						|
 | 
						|
                if value is PASSIVE_NO_RESULT or value is NO_VALUE:
 | 
						|
                    return value
 | 
						|
                elif value is ATTR_WAS_SET:
 | 
						|
                    try:
 | 
						|
                        return dict_[key]
 | 
						|
                    except KeyError as err:
 | 
						|
                        # TODO: no test coverage here.
 | 
						|
                        util.raise_(
 | 
						|
                            KeyError(
 | 
						|
                                "Deferred loader for attribute "
 | 
						|
                                "%r failed to populate "
 | 
						|
                                "correctly" % key
 | 
						|
                            ),
 | 
						|
                            replace_context=err,
 | 
						|
                        )
 | 
						|
                elif value is not ATTR_EMPTY:
 | 
						|
                    return self.set_committed_value(state, dict_, value)
 | 
						|
 | 
						|
            if not passive & INIT_OK:
 | 
						|
                return NO_VALUE
 | 
						|
            else:
 | 
						|
                return self._default_value(state, dict_)
 | 
						|
 | 
						|
    def _fire_loader_callables(self, state, key, passive):
 | 
						|
        if (
 | 
						|
            self.accepts_scalar_loader
 | 
						|
            and self.load_on_unexpire
 | 
						|
            and key in state.expired_attributes
 | 
						|
        ):
 | 
						|
            return state._load_expired(state, passive)
 | 
						|
        elif key in state.callables:
 | 
						|
            callable_ = state.callables[key]
 | 
						|
            return callable_(state, passive)
 | 
						|
        elif self.callable_:
 | 
						|
            return self.callable_(state, passive)
 | 
						|
        else:
 | 
						|
            return ATTR_EMPTY
 | 
						|
 | 
						|
    def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
 | 
						|
        self.set(state, dict_, value, initiator, passive=passive)
 | 
						|
 | 
						|
    def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
 | 
						|
        self.set(
 | 
						|
            state, dict_, None, initiator, passive=passive, check_old=value
 | 
						|
        )
 | 
						|
 | 
						|
    def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
 | 
						|
        self.set(
 | 
						|
            state,
 | 
						|
            dict_,
 | 
						|
            None,
 | 
						|
            initiator,
 | 
						|
            passive=passive,
 | 
						|
            check_old=value,
 | 
						|
            pop=True,
 | 
						|
        )
 | 
						|
 | 
						|
    def set(
 | 
						|
        self,
 | 
						|
        state,
 | 
						|
        dict_,
 | 
						|
        value,
 | 
						|
        initiator,
 | 
						|
        passive=PASSIVE_OFF,
 | 
						|
        check_old=None,
 | 
						|
        pop=False,
 | 
						|
    ):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
    def get_committed_value(self, state, dict_, passive=PASSIVE_OFF):
 | 
						|
        """return the unchanged value of this attribute"""
 | 
						|
 | 
						|
        if self.key in state.committed_state:
 | 
						|
            value = state.committed_state[self.key]
 | 
						|
            if value is NO_VALUE:
 | 
						|
                return None
 | 
						|
            else:
 | 
						|
                return value
 | 
						|
        else:
 | 
						|
            return self.get(state, dict_, passive=passive)
 | 
						|
 | 
						|
    def set_committed_value(self, state, dict_, value):
 | 
						|
        """set an attribute value on the given instance and 'commit' it."""
 | 
						|
 | 
						|
        dict_[self.key] = value
 | 
						|
        state._commit(dict_, [self.key])
 | 
						|
        return value
 | 
						|
 | 
						|
 | 
						|
class ScalarAttributeImpl(AttributeImpl):
 | 
						|
    """represents a scalar value-holding InstrumentedAttribute."""
 | 
						|
 | 
						|
    default_accepts_scalar_loader = True
 | 
						|
    uses_objects = False
 | 
						|
    supports_population = True
 | 
						|
    collection = False
 | 
						|
    dynamic = False
 | 
						|
 | 
						|
    __slots__ = "_replace_token", "_append_token", "_remove_token"
 | 
						|
 | 
						|
    def __init__(self, *arg, **kw):
 | 
						|
        super(ScalarAttributeImpl, self).__init__(*arg, **kw)
 | 
						|
        self._replace_token = self._append_token = Event(self, OP_REPLACE)
 | 
						|
        self._remove_token = Event(self, OP_REMOVE)
 | 
						|
 | 
						|
    def delete(self, state, dict_):
 | 
						|
        if self.dispatch._active_history:
 | 
						|
            old = self.get(state, dict_, PASSIVE_RETURN_NO_VALUE)
 | 
						|
        else:
 | 
						|
            old = dict_.get(self.key, NO_VALUE)
 | 
						|
 | 
						|
        if self.dispatch.remove:
 | 
						|
            self.fire_remove_event(state, dict_, old, self._remove_token)
 | 
						|
        state._modified_event(dict_, self, old)
 | 
						|
 | 
						|
        existing = dict_.pop(self.key, NO_VALUE)
 | 
						|
        if (
 | 
						|
            existing is NO_VALUE
 | 
						|
            and old is NO_VALUE
 | 
						|
            and not state.expired
 | 
						|
            and self.key not in state.expired_attributes
 | 
						|
        ):
 | 
						|
            raise AttributeError("%s object does not have a value" % self)
 | 
						|
 | 
						|
    def get_history(self, state, dict_, passive=PASSIVE_OFF):
 | 
						|
        if self.key in dict_:
 | 
						|
            return History.from_scalar_attribute(self, state, dict_[self.key])
 | 
						|
        elif self.key in state.committed_state:
 | 
						|
            return History.from_scalar_attribute(self, state, NO_VALUE)
 | 
						|
        else:
 | 
						|
            if passive & INIT_OK:
 | 
						|
                passive ^= INIT_OK
 | 
						|
            current = self.get(state, dict_, passive=passive)
 | 
						|
            if current is PASSIVE_NO_RESULT:
 | 
						|
                return HISTORY_BLANK
 | 
						|
            else:
 | 
						|
                return History.from_scalar_attribute(self, state, current)
 | 
						|
 | 
						|
    def set(
 | 
						|
        self,
 | 
						|
        state,
 | 
						|
        dict_,
 | 
						|
        value,
 | 
						|
        initiator,
 | 
						|
        passive=PASSIVE_OFF,
 | 
						|
        check_old=None,
 | 
						|
        pop=False,
 | 
						|
    ):
 | 
						|
        if self.dispatch._active_history:
 | 
						|
            old = self.get(state, dict_, PASSIVE_RETURN_NO_VALUE)
 | 
						|
        else:
 | 
						|
            old = dict_.get(self.key, NO_VALUE)
 | 
						|
 | 
						|
        if self.dispatch.set:
 | 
						|
            value = self.fire_replace_event(
 | 
						|
                state, dict_, value, old, initiator
 | 
						|
            )
 | 
						|
        state._modified_event(dict_, self, old)
 | 
						|
        dict_[self.key] = value
 | 
						|
 | 
						|
    def fire_replace_event(self, state, dict_, value, previous, initiator):
 | 
						|
        for fn in self.dispatch.set:
 | 
						|
            value = fn(
 | 
						|
                state, value, previous, initiator or self._replace_token
 | 
						|
            )
 | 
						|
        return value
 | 
						|
 | 
						|
    def fire_remove_event(self, state, dict_, value, initiator):
 | 
						|
        for fn in self.dispatch.remove:
 | 
						|
            fn(state, value, initiator or self._remove_token)
 | 
						|
 | 
						|
    @property
 | 
						|
    def type(self):
 | 
						|
        self.property.columns[0].type
 | 
						|
 | 
						|
 | 
						|
class ScalarObjectAttributeImpl(ScalarAttributeImpl):
 | 
						|
    """represents a scalar-holding InstrumentedAttribute,
 | 
						|
    where the target object is also instrumented.
 | 
						|
 | 
						|
    Adds events to delete/set operations.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    default_accepts_scalar_loader = False
 | 
						|
    uses_objects = True
 | 
						|
    supports_population = True
 | 
						|
    collection = False
 | 
						|
 | 
						|
    __slots__ = ()
 | 
						|
 | 
						|
    def delete(self, state, dict_):
 | 
						|
        if self.dispatch._active_history:
 | 
						|
            old = self.get(
 | 
						|
                state,
 | 
						|
                dict_,
 | 
						|
                passive=PASSIVE_ONLY_PERSISTENT
 | 
						|
                | NO_AUTOFLUSH
 | 
						|
                | LOAD_AGAINST_COMMITTED,
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            old = self.get(
 | 
						|
                state,
 | 
						|
                dict_,
 | 
						|
                passive=PASSIVE_NO_FETCH ^ INIT_OK
 | 
						|
                | LOAD_AGAINST_COMMITTED
 | 
						|
                | NO_RAISE,
 | 
						|
            )
 | 
						|
 | 
						|
        self.fire_remove_event(state, dict_, old, self._remove_token)
 | 
						|
 | 
						|
        existing = dict_.pop(self.key, NO_VALUE)
 | 
						|
 | 
						|
        # if the attribute is expired, we currently have no way to tell
 | 
						|
        # that an object-attribute was expired vs. not loaded.   So
 | 
						|
        # for this test, we look to see if the object has a DB identity.
 | 
						|
        if (
 | 
						|
            existing is NO_VALUE
 | 
						|
            and old is not PASSIVE_NO_RESULT
 | 
						|
            and state.key is None
 | 
						|
        ):
 | 
						|
            raise AttributeError("%s object does not have a value" % self)
 | 
						|
 | 
						|
    def get_history(self, state, dict_, passive=PASSIVE_OFF):
 | 
						|
        if self.key in dict_:
 | 
						|
            current = dict_[self.key]
 | 
						|
        else:
 | 
						|
            if passive & INIT_OK:
 | 
						|
                passive ^= INIT_OK
 | 
						|
            current = self.get(state, dict_, passive=passive)
 | 
						|
            if current is PASSIVE_NO_RESULT:
 | 
						|
                return HISTORY_BLANK
 | 
						|
 | 
						|
        if not self._deferred_history:
 | 
						|
            return History.from_object_attribute(self, state, current)
 | 
						|
        else:
 | 
						|
            original = state.committed_state.get(self.key, _NO_HISTORY)
 | 
						|
            if original is PASSIVE_NO_RESULT:
 | 
						|
 | 
						|
                loader_passive = passive | (
 | 
						|
                    PASSIVE_ONLY_PERSISTENT
 | 
						|
                    | NO_AUTOFLUSH
 | 
						|
                    | LOAD_AGAINST_COMMITTED
 | 
						|
                    | NO_RAISE
 | 
						|
                    | DEFERRED_HISTORY_LOAD
 | 
						|
                )
 | 
						|
                original = self._fire_loader_callables(
 | 
						|
                    state, self.key, loader_passive
 | 
						|
                )
 | 
						|
            return History.from_object_attribute(
 | 
						|
                self, state, current, original=original
 | 
						|
            )
 | 
						|
 | 
						|
    def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
 | 
						|
        if self.key in dict_:
 | 
						|
            current = dict_[self.key]
 | 
						|
        elif passive & CALLABLES_OK:
 | 
						|
            current = self.get(state, dict_, passive=passive)
 | 
						|
        else:
 | 
						|
            return []
 | 
						|
 | 
						|
        # can't use __hash__(), can't use __eq__() here
 | 
						|
        if (
 | 
						|
            current is not None
 | 
						|
            and current is not PASSIVE_NO_RESULT
 | 
						|
            and current is not NO_VALUE
 | 
						|
        ):
 | 
						|
            ret = [(instance_state(current), current)]
 | 
						|
        else:
 | 
						|
            ret = [(None, None)]
 | 
						|
 | 
						|
        if self.key in state.committed_state:
 | 
						|
            original = state.committed_state[self.key]
 | 
						|
            if (
 | 
						|
                original is not None
 | 
						|
                and original is not PASSIVE_NO_RESULT
 | 
						|
                and original is not NO_VALUE
 | 
						|
                and original is not current
 | 
						|
            ):
 | 
						|
 | 
						|
                ret.append((instance_state(original), original))
 | 
						|
        return ret
 | 
						|
 | 
						|
    def set(
 | 
						|
        self,
 | 
						|
        state,
 | 
						|
        dict_,
 | 
						|
        value,
 | 
						|
        initiator,
 | 
						|
        passive=PASSIVE_OFF,
 | 
						|
        check_old=None,
 | 
						|
        pop=False,
 | 
						|
    ):
 | 
						|
        """Set a value on the given InstanceState."""
 | 
						|
 | 
						|
        if self.dispatch._active_history:
 | 
						|
            old = self.get(
 | 
						|
                state,
 | 
						|
                dict_,
 | 
						|
                passive=PASSIVE_ONLY_PERSISTENT
 | 
						|
                | NO_AUTOFLUSH
 | 
						|
                | LOAD_AGAINST_COMMITTED,
 | 
						|
            )
 | 
						|
        else:
 | 
						|
            old = self.get(
 | 
						|
                state,
 | 
						|
                dict_,
 | 
						|
                passive=PASSIVE_NO_FETCH ^ INIT_OK
 | 
						|
                | LOAD_AGAINST_COMMITTED
 | 
						|
                | NO_RAISE,
 | 
						|
            )
 | 
						|
 | 
						|
        if (
 | 
						|
            check_old is not None
 | 
						|
            and old is not PASSIVE_NO_RESULT
 | 
						|
            and check_old is not old
 | 
						|
        ):
 | 
						|
            if pop:
 | 
						|
                return
 | 
						|
            else:
 | 
						|
                raise ValueError(
 | 
						|
                    "Object %s not associated with %s on attribute '%s'"
 | 
						|
                    % (instance_str(check_old), state_str(state), self.key)
 | 
						|
                )
 | 
						|
 | 
						|
        value = self.fire_replace_event(state, dict_, value, old, initiator)
 | 
						|
        dict_[self.key] = value
 | 
						|
 | 
						|
    def fire_remove_event(self, state, dict_, value, initiator):
 | 
						|
        if self.trackparent and value not in (
 | 
						|
            None,
 | 
						|
            PASSIVE_NO_RESULT,
 | 
						|
            NO_VALUE,
 | 
						|
        ):
 | 
						|
            self.sethasparent(instance_state(value), state, False)
 | 
						|
 | 
						|
        for fn in self.dispatch.remove:
 | 
						|
            fn(state, value, initiator or self._remove_token)
 | 
						|
 | 
						|
        state._modified_event(dict_, self, value)
 | 
						|
 | 
						|
    def fire_replace_event(self, state, dict_, value, previous, initiator):
 | 
						|
        if self.trackparent:
 | 
						|
            if previous is not value and previous not in (
 | 
						|
                None,
 | 
						|
                PASSIVE_NO_RESULT,
 | 
						|
                NO_VALUE,
 | 
						|
            ):
 | 
						|
                self.sethasparent(instance_state(previous), state, False)
 | 
						|
 | 
						|
        for fn in self.dispatch.set:
 | 
						|
            value = fn(
 | 
						|
                state, value, previous, initiator or self._replace_token
 | 
						|
            )
 | 
						|
 | 
						|
        state._modified_event(dict_, self, previous)
 | 
						|
 | 
						|
        if self.trackparent:
 | 
						|
            if value is not None:
 | 
						|
                self.sethasparent(instance_state(value), state, True)
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
 | 
						|
class CollectionAttributeImpl(AttributeImpl):
 | 
						|
    """A collection-holding attribute that instruments changes in membership.
 | 
						|
 | 
						|
    Only handles collections of instrumented objects.
 | 
						|
 | 
						|
    InstrumentedCollectionAttribute holds an arbitrary, user-specified
 | 
						|
    container object (defaulting to a list) and brokers access to the
 | 
						|
    CollectionAdapter, a "view" onto that object that presents consistent bag
 | 
						|
    semantics to the orm layer independent of the user data implementation.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    default_accepts_scalar_loader = False
 | 
						|
    uses_objects = True
 | 
						|
    supports_population = True
 | 
						|
    collection = True
 | 
						|
    dynamic = False
 | 
						|
 | 
						|
    __slots__ = (
 | 
						|
        "copy",
 | 
						|
        "collection_factory",
 | 
						|
        "_append_token",
 | 
						|
        "_remove_token",
 | 
						|
        "_bulk_replace_token",
 | 
						|
        "_duck_typed_as",
 | 
						|
    )
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        class_,
 | 
						|
        key,
 | 
						|
        callable_,
 | 
						|
        dispatch,
 | 
						|
        typecallable=None,
 | 
						|
        trackparent=False,
 | 
						|
        copy_function=None,
 | 
						|
        compare_function=None,
 | 
						|
        **kwargs
 | 
						|
    ):
 | 
						|
        super(CollectionAttributeImpl, self).__init__(
 | 
						|
            class_,
 | 
						|
            key,
 | 
						|
            callable_,
 | 
						|
            dispatch,
 | 
						|
            trackparent=trackparent,
 | 
						|
            compare_function=compare_function,
 | 
						|
            **kwargs
 | 
						|
        )
 | 
						|
 | 
						|
        if copy_function is None:
 | 
						|
            copy_function = self.__copy
 | 
						|
        self.copy = copy_function
 | 
						|
        self.collection_factory = typecallable
 | 
						|
        self._append_token = Event(self, OP_APPEND)
 | 
						|
        self._remove_token = Event(self, OP_REMOVE)
 | 
						|
        self._bulk_replace_token = Event(self, OP_BULK_REPLACE)
 | 
						|
        self._duck_typed_as = util.duck_type_collection(
 | 
						|
            self.collection_factory()
 | 
						|
        )
 | 
						|
 | 
						|
        if getattr(self.collection_factory, "_sa_linker", None):
 | 
						|
 | 
						|
            @event.listens_for(self, "init_collection")
 | 
						|
            def link(target, collection, collection_adapter):
 | 
						|
                collection._sa_linker(collection_adapter)
 | 
						|
 | 
						|
            @event.listens_for(self, "dispose_collection")
 | 
						|
            def unlink(target, collection, collection_adapter):
 | 
						|
                collection._sa_linker(None)
 | 
						|
 | 
						|
    def __copy(self, item):
 | 
						|
        return [y for y in collections.collection_adapter(item)]
 | 
						|
 | 
						|
    def get_history(self, state, dict_, passive=PASSIVE_OFF):
 | 
						|
        current = self.get(state, dict_, passive=passive)
 | 
						|
        if current is PASSIVE_NO_RESULT:
 | 
						|
            return HISTORY_BLANK
 | 
						|
        else:
 | 
						|
            return History.from_collection(self, state, current)
 | 
						|
 | 
						|
    def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
 | 
						|
        # NOTE: passive is ignored here at the moment
 | 
						|
 | 
						|
        if self.key not in dict_:
 | 
						|
            return []
 | 
						|
 | 
						|
        current = dict_[self.key]
 | 
						|
        current = getattr(current, "_sa_adapter")
 | 
						|
 | 
						|
        if self.key in state.committed_state:
 | 
						|
            original = state.committed_state[self.key]
 | 
						|
            if original is not NO_VALUE:
 | 
						|
                current_states = [
 | 
						|
                    ((c is not None) and instance_state(c) or None, c)
 | 
						|
                    for c in current
 | 
						|
                ]
 | 
						|
                original_states = [
 | 
						|
                    ((c is not None) and instance_state(c) or None, c)
 | 
						|
                    for c in original
 | 
						|
                ]
 | 
						|
 | 
						|
                current_set = dict(current_states)
 | 
						|
                original_set = dict(original_states)
 | 
						|
 | 
						|
                return (
 | 
						|
                    [
 | 
						|
                        (s, o)
 | 
						|
                        for s, o in current_states
 | 
						|
                        if s not in original_set
 | 
						|
                    ]
 | 
						|
                    + [(s, o) for s, o in current_states if s in original_set]
 | 
						|
                    + [
 | 
						|
                        (s, o)
 | 
						|
                        for s, o in original_states
 | 
						|
                        if s not in current_set
 | 
						|
                    ]
 | 
						|
                )
 | 
						|
 | 
						|
        return [(instance_state(o), o) for o in current]
 | 
						|
 | 
						|
    def fire_append_event(self, state, dict_, value, initiator):
 | 
						|
        for fn in self.dispatch.append:
 | 
						|
            value = fn(state, value, initiator or self._append_token)
 | 
						|
 | 
						|
        state._modified_event(dict_, self, NO_VALUE, True)
 | 
						|
 | 
						|
        if self.trackparent and value is not None:
 | 
						|
            self.sethasparent(instance_state(value), state, True)
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
    def fire_append_wo_mutation_event(self, state, dict_, value, initiator):
 | 
						|
        for fn in self.dispatch.append_wo_mutation:
 | 
						|
            value = fn(state, value, initiator or self._append_token)
 | 
						|
 | 
						|
        return value
 | 
						|
 | 
						|
    def fire_pre_remove_event(self, state, dict_, initiator):
 | 
						|
        """A special event used for pop() operations.
 | 
						|
 | 
						|
        The "remove" event needs to have the item to be removed passed to
 | 
						|
        it, which in the case of pop from a set, we don't have a way to access
 | 
						|
        the item before the operation.   the event is used for all pop()
 | 
						|
        operations (even though set.pop is the one where it is really needed).
 | 
						|
 | 
						|
        """
 | 
						|
        state._modified_event(dict_, self, NO_VALUE, True)
 | 
						|
 | 
						|
    def fire_remove_event(self, state, dict_, value, initiator):
 | 
						|
        if self.trackparent and value is not None:
 | 
						|
            self.sethasparent(instance_state(value), state, False)
 | 
						|
 | 
						|
        for fn in self.dispatch.remove:
 | 
						|
            fn(state, value, initiator or self._remove_token)
 | 
						|
 | 
						|
        state._modified_event(dict_, self, NO_VALUE, True)
 | 
						|
 | 
						|
    def delete(self, state, dict_):
 | 
						|
        if self.key not in dict_:
 | 
						|
            return
 | 
						|
 | 
						|
        state._modified_event(dict_, self, NO_VALUE, True)
 | 
						|
 | 
						|
        collection = self.get_collection(state, state.dict)
 | 
						|
        collection.clear_with_event()
 | 
						|
 | 
						|
        # key is always present because we checked above.  e.g.
 | 
						|
        # del is a no-op if collection not present.
 | 
						|
        del dict_[self.key]
 | 
						|
 | 
						|
    def _default_value(self, state, dict_):
 | 
						|
        """Produce an empty collection for an un-initialized attribute"""
 | 
						|
 | 
						|
        assert self.key not in dict_, (
 | 
						|
            "_default_value should only be invoked for an "
 | 
						|
            "uninitialized or expired attribute"
 | 
						|
        )
 | 
						|
 | 
						|
        if self.key in state._empty_collections:
 | 
						|
            return state._empty_collections[self.key]
 | 
						|
 | 
						|
        adapter, user_data = self._initialize_collection(state)
 | 
						|
        adapter._set_empty(user_data)
 | 
						|
        return user_data
 | 
						|
 | 
						|
    def _initialize_collection(self, state):
 | 
						|
 | 
						|
        adapter, collection = state.manager.initialize_collection(
 | 
						|
            self.key, state, self.collection_factory
 | 
						|
        )
 | 
						|
 | 
						|
        self.dispatch.init_collection(state, collection, adapter)
 | 
						|
 | 
						|
        return adapter, collection
 | 
						|
 | 
						|
    def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
 | 
						|
        collection = self.get_collection(state, dict_, passive=passive)
 | 
						|
        if collection is PASSIVE_NO_RESULT:
 | 
						|
            value = self.fire_append_event(state, dict_, value, initiator)
 | 
						|
            assert (
 | 
						|
                self.key not in dict_
 | 
						|
            ), "Collection was loaded during event handling."
 | 
						|
            state._get_pending_mutation(self.key).append(value)
 | 
						|
        else:
 | 
						|
            collection.append_with_event(value, initiator)
 | 
						|
 | 
						|
    def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
 | 
						|
        collection = self.get_collection(state, state.dict, passive=passive)
 | 
						|
        if collection is PASSIVE_NO_RESULT:
 | 
						|
            self.fire_remove_event(state, dict_, value, initiator)
 | 
						|
            assert (
 | 
						|
                self.key not in dict_
 | 
						|
            ), "Collection was loaded during event handling."
 | 
						|
            state._get_pending_mutation(self.key).remove(value)
 | 
						|
        else:
 | 
						|
            collection.remove_with_event(value, initiator)
 | 
						|
 | 
						|
    def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
 | 
						|
        try:
 | 
						|
            # TODO: better solution here would be to add
 | 
						|
            # a "popper" role to collections.py to complement
 | 
						|
            # "remover".
 | 
						|
            self.remove(state, dict_, value, initiator, passive=passive)
 | 
						|
        except (ValueError, KeyError, IndexError):
 | 
						|
            pass
 | 
						|
 | 
						|
    def set(
 | 
						|
        self,
 | 
						|
        state,
 | 
						|
        dict_,
 | 
						|
        value,
 | 
						|
        initiator=None,
 | 
						|
        passive=PASSIVE_OFF,
 | 
						|
        check_old=None,
 | 
						|
        pop=False,
 | 
						|
        _adapt=True,
 | 
						|
    ):
 | 
						|
        iterable = orig_iterable = value
 | 
						|
 | 
						|
        # pulling a new collection first so that an adaptation exception does
 | 
						|
        # not trigger a lazy load of the old collection.
 | 
						|
        new_collection, user_data = self._initialize_collection(state)
 | 
						|
        if _adapt:
 | 
						|
            if new_collection._converter is not None:
 | 
						|
                iterable = new_collection._converter(iterable)
 | 
						|
            else:
 | 
						|
                setting_type = util.duck_type_collection(iterable)
 | 
						|
                receiving_type = self._duck_typed_as
 | 
						|
 | 
						|
                if setting_type is not receiving_type:
 | 
						|
                    given = (
 | 
						|
                        iterable is None
 | 
						|
                        and "None"
 | 
						|
                        or iterable.__class__.__name__
 | 
						|
                    )
 | 
						|
                    wanted = self._duck_typed_as.__name__
 | 
						|
                    raise TypeError(
 | 
						|
                        "Incompatible collection type: %s is not %s-like"
 | 
						|
                        % (given, wanted)
 | 
						|
                    )
 | 
						|
 | 
						|
                # If the object is an adapted collection, return the (iterable)
 | 
						|
                # adapter.
 | 
						|
                if hasattr(iterable, "_sa_iterator"):
 | 
						|
                    iterable = iterable._sa_iterator()
 | 
						|
                elif setting_type is dict:
 | 
						|
                    if util.py3k:
 | 
						|
                        iterable = iterable.values()
 | 
						|
                    else:
 | 
						|
                        iterable = getattr(
 | 
						|
                            iterable, "itervalues", iterable.values
 | 
						|
                        )()
 | 
						|
                else:
 | 
						|
                    iterable = iter(iterable)
 | 
						|
        new_values = list(iterable)
 | 
						|
 | 
						|
        evt = self._bulk_replace_token
 | 
						|
 | 
						|
        self.dispatch.bulk_replace(state, new_values, evt)
 | 
						|
 | 
						|
        old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT)
 | 
						|
        if old is PASSIVE_NO_RESULT:
 | 
						|
            old = self._default_value(state, dict_)
 | 
						|
        elif old is orig_iterable:
 | 
						|
            # ignore re-assignment of the current collection, as happens
 | 
						|
            # implicitly with in-place operators (foo.collection |= other)
 | 
						|
            return
 | 
						|
 | 
						|
        # place a copy of "old" in state.committed_state
 | 
						|
        state._modified_event(dict_, self, old, True)
 | 
						|
 | 
						|
        old_collection = old._sa_adapter
 | 
						|
 | 
						|
        dict_[self.key] = user_data
 | 
						|
 | 
						|
        collections.bulk_replace(
 | 
						|
            new_values, old_collection, new_collection, initiator=evt
 | 
						|
        )
 | 
						|
 | 
						|
        self._dispose_previous_collection(state, old, old_collection, True)
 | 
						|
 | 
						|
    def _dispose_previous_collection(
 | 
						|
        self, state, collection, adapter, fire_event
 | 
						|
    ):
 | 
						|
        del collection._sa_adapter
 | 
						|
 | 
						|
        # discarding old collection make sure it is not referenced in empty
 | 
						|
        # collections.
 | 
						|
        state._empty_collections.pop(self.key, None)
 | 
						|
        if fire_event:
 | 
						|
            self.dispatch.dispose_collection(state, collection, adapter)
 | 
						|
 | 
						|
    def _invalidate_collection(self, collection):
 | 
						|
        adapter = getattr(collection, "_sa_adapter")
 | 
						|
        adapter.invalidated = True
 | 
						|
 | 
						|
    def set_committed_value(self, state, dict_, value):
 | 
						|
        """Set an attribute value on the given instance and 'commit' it."""
 | 
						|
 | 
						|
        collection, user_data = self._initialize_collection(state)
 | 
						|
 | 
						|
        if value:
 | 
						|
            collection.append_multiple_without_event(value)
 | 
						|
 | 
						|
        state.dict[self.key] = user_data
 | 
						|
 | 
						|
        state._commit(dict_, [self.key])
 | 
						|
 | 
						|
        if self.key in state._pending_mutations:
 | 
						|
            # pending items exist.  issue a modified event,
 | 
						|
            # add/remove new items.
 | 
						|
            state._modified_event(dict_, self, user_data, True)
 | 
						|
 | 
						|
            pending = state._pending_mutations.pop(self.key)
 | 
						|
            added = pending.added_items
 | 
						|
            removed = pending.deleted_items
 | 
						|
            for item in added:
 | 
						|
                collection.append_without_event(item)
 | 
						|
            for item in removed:
 | 
						|
                collection.remove_without_event(item)
 | 
						|
 | 
						|
        return user_data
 | 
						|
 | 
						|
    def get_collection(
 | 
						|
        self, state, dict_, user_data=None, passive=PASSIVE_OFF
 | 
						|
    ):
 | 
						|
        """Retrieve the CollectionAdapter associated with the given state.
 | 
						|
 | 
						|
        if user_data is None, retrieves it from the state using normal
 | 
						|
        "get()" rules, which will fire lazy callables or return the "empty"
 | 
						|
        collection value.
 | 
						|
 | 
						|
        """
 | 
						|
        if user_data is None:
 | 
						|
            user_data = self.get(state, dict_, passive=passive)
 | 
						|
            if user_data is PASSIVE_NO_RESULT:
 | 
						|
                return user_data
 | 
						|
 | 
						|
        return user_data._sa_adapter
 | 
						|
 | 
						|
 | 
						|
def backref_listeners(attribute, key, uselist):
 | 
						|
    """Apply listeners to synchronize a two-way relationship."""
 | 
						|
 | 
						|
    # use easily recognizable names for stack traces.
 | 
						|
 | 
						|
    # in the sections marked "tokens to test for a recursive loop",
 | 
						|
    # this is somewhat brittle and very performance-sensitive logic
 | 
						|
    # that is specific to how we might arrive at each event.  a marker
 | 
						|
    # that can target us directly to arguments being invoked against
 | 
						|
    # the impl might be simpler, but could interfere with other systems.
 | 
						|
 | 
						|
    parent_token = attribute.impl.parent_token
 | 
						|
    parent_impl = attribute.impl
 | 
						|
 | 
						|
    def _acceptable_key_err(child_state, initiator, child_impl):
 | 
						|
        raise ValueError(
 | 
						|
            "Bidirectional attribute conflict detected: "
 | 
						|
            'Passing object %s to attribute "%s" '
 | 
						|
            'triggers a modify event on attribute "%s" '
 | 
						|
            'via the backref "%s".'
 | 
						|
            % (
 | 
						|
                state_str(child_state),
 | 
						|
                initiator.parent_token,
 | 
						|
                child_impl.parent_token,
 | 
						|
                attribute.impl.parent_token,
 | 
						|
            )
 | 
						|
        )
 | 
						|
 | 
						|
    def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
 | 
						|
        if oldchild is child:
 | 
						|
            return child
 | 
						|
        if (
 | 
						|
            oldchild is not None
 | 
						|
            and oldchild is not PASSIVE_NO_RESULT
 | 
						|
            and oldchild is not NO_VALUE
 | 
						|
        ):
 | 
						|
            # With lazy=None, there's no guarantee that the full collection is
 | 
						|
            # present when updating via a backref.
 | 
						|
            old_state, old_dict = (
 | 
						|
                instance_state(oldchild),
 | 
						|
                instance_dict(oldchild),
 | 
						|
            )
 | 
						|
            impl = old_state.manager[key].impl
 | 
						|
 | 
						|
            # tokens to test for a recursive loop.
 | 
						|
            if not impl.collection and not impl.dynamic:
 | 
						|
                check_recursive_token = impl._replace_token
 | 
						|
            else:
 | 
						|
                check_recursive_token = impl._remove_token
 | 
						|
 | 
						|
            if initiator is not check_recursive_token:
 | 
						|
                impl.pop(
 | 
						|
                    old_state,
 | 
						|
                    old_dict,
 | 
						|
                    state.obj(),
 | 
						|
                    parent_impl._append_token,
 | 
						|
                    passive=PASSIVE_NO_FETCH,
 | 
						|
                )
 | 
						|
 | 
						|
        if child is not None:
 | 
						|
            child_state, child_dict = (
 | 
						|
                instance_state(child),
 | 
						|
                instance_dict(child),
 | 
						|
            )
 | 
						|
            child_impl = child_state.manager[key].impl
 | 
						|
 | 
						|
            if (
 | 
						|
                initiator.parent_token is not parent_token
 | 
						|
                and initiator.parent_token is not child_impl.parent_token
 | 
						|
            ):
 | 
						|
                _acceptable_key_err(state, initiator, child_impl)
 | 
						|
 | 
						|
            # tokens to test for a recursive loop.
 | 
						|
            check_append_token = child_impl._append_token
 | 
						|
            check_bulk_replace_token = (
 | 
						|
                child_impl._bulk_replace_token
 | 
						|
                if child_impl.collection
 | 
						|
                else None
 | 
						|
            )
 | 
						|
 | 
						|
            if (
 | 
						|
                initiator is not check_append_token
 | 
						|
                and initiator is not check_bulk_replace_token
 | 
						|
            ):
 | 
						|
                child_impl.append(
 | 
						|
                    child_state,
 | 
						|
                    child_dict,
 | 
						|
                    state.obj(),
 | 
						|
                    initiator,
 | 
						|
                    passive=PASSIVE_NO_FETCH,
 | 
						|
                )
 | 
						|
        return child
 | 
						|
 | 
						|
    def emit_backref_from_collection_append_event(state, child, initiator):
 | 
						|
        if child is None:
 | 
						|
            return
 | 
						|
 | 
						|
        child_state, child_dict = instance_state(child), instance_dict(child)
 | 
						|
        child_impl = child_state.manager[key].impl
 | 
						|
 | 
						|
        if (
 | 
						|
            initiator.parent_token is not parent_token
 | 
						|
            and initiator.parent_token is not child_impl.parent_token
 | 
						|
        ):
 | 
						|
            _acceptable_key_err(state, initiator, child_impl)
 | 
						|
 | 
						|
        # tokens to test for a recursive loop.
 | 
						|
        check_append_token = child_impl._append_token
 | 
						|
        check_bulk_replace_token = (
 | 
						|
            child_impl._bulk_replace_token if child_impl.collection else None
 | 
						|
        )
 | 
						|
 | 
						|
        if (
 | 
						|
            initiator is not check_append_token
 | 
						|
            and initiator is not check_bulk_replace_token
 | 
						|
        ):
 | 
						|
            child_impl.append(
 | 
						|
                child_state,
 | 
						|
                child_dict,
 | 
						|
                state.obj(),
 | 
						|
                initiator,
 | 
						|
                passive=PASSIVE_NO_FETCH,
 | 
						|
            )
 | 
						|
        return child
 | 
						|
 | 
						|
    def emit_backref_from_collection_remove_event(state, child, initiator):
 | 
						|
        if (
 | 
						|
            child is not None
 | 
						|
            and child is not PASSIVE_NO_RESULT
 | 
						|
            and child is not NO_VALUE
 | 
						|
        ):
 | 
						|
            child_state, child_dict = (
 | 
						|
                instance_state(child),
 | 
						|
                instance_dict(child),
 | 
						|
            )
 | 
						|
            child_impl = child_state.manager[key].impl
 | 
						|
 | 
						|
            # tokens to test for a recursive loop.
 | 
						|
            if not child_impl.collection and not child_impl.dynamic:
 | 
						|
                check_remove_token = child_impl._remove_token
 | 
						|
                check_replace_token = child_impl._replace_token
 | 
						|
                check_for_dupes_on_remove = uselist and not parent_impl.dynamic
 | 
						|
            else:
 | 
						|
                check_remove_token = child_impl._remove_token
 | 
						|
                check_replace_token = (
 | 
						|
                    child_impl._bulk_replace_token
 | 
						|
                    if child_impl.collection
 | 
						|
                    else None
 | 
						|
                )
 | 
						|
                check_for_dupes_on_remove = False
 | 
						|
 | 
						|
            if (
 | 
						|
                initiator is not check_remove_token
 | 
						|
                and initiator is not check_replace_token
 | 
						|
            ):
 | 
						|
 | 
						|
                if not check_for_dupes_on_remove or not util.has_dupes(
 | 
						|
                    # when this event is called, the item is usually
 | 
						|
                    # present in the list, except for a pop() operation.
 | 
						|
                    state.dict[parent_impl.key],
 | 
						|
                    child,
 | 
						|
                ):
 | 
						|
                    child_impl.pop(
 | 
						|
                        child_state,
 | 
						|
                        child_dict,
 | 
						|
                        state.obj(),
 | 
						|
                        initiator,
 | 
						|
                        passive=PASSIVE_NO_FETCH,
 | 
						|
                    )
 | 
						|
 | 
						|
    if uselist:
 | 
						|
        event.listen(
 | 
						|
            attribute,
 | 
						|
            "append",
 | 
						|
            emit_backref_from_collection_append_event,
 | 
						|
            retval=True,
 | 
						|
            raw=True,
 | 
						|
        )
 | 
						|
    else:
 | 
						|
        event.listen(
 | 
						|
            attribute,
 | 
						|
            "set",
 | 
						|
            emit_backref_from_scalar_set_event,
 | 
						|
            retval=True,
 | 
						|
            raw=True,
 | 
						|
        )
 | 
						|
    # TODO: need coverage in test/orm/ of remove event
 | 
						|
    event.listen(
 | 
						|
        attribute,
 | 
						|
        "remove",
 | 
						|
        emit_backref_from_collection_remove_event,
 | 
						|
        retval=True,
 | 
						|
        raw=True,
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
_NO_HISTORY = util.symbol("NO_HISTORY")
 | 
						|
_NO_STATE_SYMBOLS = frozenset([id(PASSIVE_NO_RESULT), id(NO_VALUE)])
 | 
						|
 | 
						|
 | 
						|
class History(util.namedtuple("History", ["added", "unchanged", "deleted"])):
 | 
						|
    """A 3-tuple of added, unchanged and deleted values,
 | 
						|
    representing the changes which have occurred on an instrumented
 | 
						|
    attribute.
 | 
						|
 | 
						|
    The easiest way to get a :class:`.History` object for a particular
 | 
						|
    attribute on an object is to use the :func:`_sa.inspect` function::
 | 
						|
 | 
						|
        from sqlalchemy import inspect
 | 
						|
 | 
						|
        hist = inspect(myobject).attrs.myattribute.history
 | 
						|
 | 
						|
    Each tuple member is an iterable sequence:
 | 
						|
 | 
						|
    * ``added`` - the collection of items added to the attribute (the first
 | 
						|
      tuple element).
 | 
						|
 | 
						|
    * ``unchanged`` - the collection of items that have not changed on the
 | 
						|
      attribute (the second tuple element).
 | 
						|
 | 
						|
    * ``deleted`` - the collection of items that have been removed from the
 | 
						|
      attribute (the third tuple element).
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __bool__(self):
 | 
						|
        return self != HISTORY_BLANK
 | 
						|
 | 
						|
    __nonzero__ = __bool__
 | 
						|
 | 
						|
    def empty(self):
 | 
						|
        """Return True if this :class:`.History` has no changes
 | 
						|
        and no existing, unchanged state.
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        return not bool((self.added or self.deleted) or self.unchanged)
 | 
						|
 | 
						|
    def sum(self):
 | 
						|
        """Return a collection of added + unchanged + deleted."""
 | 
						|
 | 
						|
        return (
 | 
						|
            (self.added or []) + (self.unchanged or []) + (self.deleted or [])
 | 
						|
        )
 | 
						|
 | 
						|
    def non_deleted(self):
 | 
						|
        """Return a collection of added + unchanged."""
 | 
						|
 | 
						|
        return (self.added or []) + (self.unchanged or [])
 | 
						|
 | 
						|
    def non_added(self):
 | 
						|
        """Return a collection of unchanged + deleted."""
 | 
						|
 | 
						|
        return (self.unchanged or []) + (self.deleted or [])
 | 
						|
 | 
						|
    def has_changes(self):
 | 
						|
        """Return True if this :class:`.History` has changes."""
 | 
						|
 | 
						|
        return bool(self.added or self.deleted)
 | 
						|
 | 
						|
    def as_state(self):
 | 
						|
        return History(
 | 
						|
            [
 | 
						|
                (c is not None) and instance_state(c) or None
 | 
						|
                for c in self.added
 | 
						|
            ],
 | 
						|
            [
 | 
						|
                (c is not None) and instance_state(c) or None
 | 
						|
                for c in self.unchanged
 | 
						|
            ],
 | 
						|
            [
 | 
						|
                (c is not None) and instance_state(c) or None
 | 
						|
                for c in self.deleted
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_scalar_attribute(cls, attribute, state, current):
 | 
						|
        original = state.committed_state.get(attribute.key, _NO_HISTORY)
 | 
						|
 | 
						|
        if original is _NO_HISTORY:
 | 
						|
            if current is NO_VALUE:
 | 
						|
                return cls((), (), ())
 | 
						|
            else:
 | 
						|
                return cls((), [current], ())
 | 
						|
        # don't let ClauseElement expressions here trip things up
 | 
						|
        elif (
 | 
						|
            current is not NO_VALUE
 | 
						|
            and attribute.is_equal(current, original) is True
 | 
						|
        ):
 | 
						|
            return cls((), [current], ())
 | 
						|
        else:
 | 
						|
            # current convention on native scalars is to not
 | 
						|
            # include information
 | 
						|
            # about missing previous value in "deleted", but
 | 
						|
            # we do include None, which helps in some primary
 | 
						|
            # key situations
 | 
						|
            if id(original) in _NO_STATE_SYMBOLS:
 | 
						|
                deleted = ()
 | 
						|
                # indicate a "del" operation occurred when we don't have
 | 
						|
                # the previous value as: ([None], (), ())
 | 
						|
                if id(current) in _NO_STATE_SYMBOLS:
 | 
						|
                    current = None
 | 
						|
            else:
 | 
						|
                deleted = [original]
 | 
						|
            if current is NO_VALUE:
 | 
						|
                return cls((), (), deleted)
 | 
						|
            else:
 | 
						|
                return cls([current], (), deleted)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_object_attribute(
 | 
						|
        cls, attribute, state, current, original=_NO_HISTORY
 | 
						|
    ):
 | 
						|
        if original is _NO_HISTORY:
 | 
						|
            original = state.committed_state.get(attribute.key, _NO_HISTORY)
 | 
						|
 | 
						|
        if original is _NO_HISTORY:
 | 
						|
            if current is NO_VALUE:
 | 
						|
                return cls((), (), ())
 | 
						|
            else:
 | 
						|
                return cls((), [current], ())
 | 
						|
        elif current is original and current is not NO_VALUE:
 | 
						|
            return cls((), [current], ())
 | 
						|
        else:
 | 
						|
            # current convention on related objects is to not
 | 
						|
            # include information
 | 
						|
            # about missing previous value in "deleted", and
 | 
						|
            # to also not include None - the dependency.py rules
 | 
						|
            # ignore the None in any case.
 | 
						|
            if id(original) in _NO_STATE_SYMBOLS or original is None:
 | 
						|
                deleted = ()
 | 
						|
                # indicate a "del" operation occurred when we don't have
 | 
						|
                # the previous value as: ([None], (), ())
 | 
						|
                if id(current) in _NO_STATE_SYMBOLS:
 | 
						|
                    current = None
 | 
						|
            else:
 | 
						|
                deleted = [original]
 | 
						|
            if current is NO_VALUE:
 | 
						|
                return cls((), (), deleted)
 | 
						|
            else:
 | 
						|
                return cls([current], (), deleted)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_collection(cls, attribute, state, current):
 | 
						|
        original = state.committed_state.get(attribute.key, _NO_HISTORY)
 | 
						|
        if current is NO_VALUE:
 | 
						|
            return cls((), (), ())
 | 
						|
 | 
						|
        current = getattr(current, "_sa_adapter")
 | 
						|
        if original is NO_VALUE:
 | 
						|
            return cls(list(current), (), ())
 | 
						|
        elif original is _NO_HISTORY:
 | 
						|
            return cls((), list(current), ())
 | 
						|
        else:
 | 
						|
 | 
						|
            current_states = [
 | 
						|
                ((c is not None) and instance_state(c) or None, c)
 | 
						|
                for c in current
 | 
						|
            ]
 | 
						|
            original_states = [
 | 
						|
                ((c is not None) and instance_state(c) or None, c)
 | 
						|
                for c in original
 | 
						|
            ]
 | 
						|
 | 
						|
            current_set = dict(current_states)
 | 
						|
            original_set = dict(original_states)
 | 
						|
 | 
						|
            return cls(
 | 
						|
                [o for s, o in current_states if s not in original_set],
 | 
						|
                [o for s, o in current_states if s in original_set],
 | 
						|
                [o for s, o in original_states if s not in current_set],
 | 
						|
            )
 | 
						|
 | 
						|
 | 
						|
HISTORY_BLANK = History(None, None, None)
 | 
						|
 | 
						|
 | 
						|
def get_history(obj, key, passive=PASSIVE_OFF):
 | 
						|
    """Return a :class:`.History` record for the given object
 | 
						|
    and attribute key.
 | 
						|
 | 
						|
    This is the **pre-flush** history for a given attribute, which is
 | 
						|
    reset each time the :class:`.Session` flushes changes to the
 | 
						|
    current database transaction.
 | 
						|
 | 
						|
    .. note::
 | 
						|
 | 
						|
        Prefer to use the :attr:`.AttributeState.history` and
 | 
						|
        :meth:`.AttributeState.load_history` accessors to retrieve the
 | 
						|
        :class:`.History` for instance attributes.
 | 
						|
 | 
						|
 | 
						|
    :param obj: an object whose class is instrumented by the
 | 
						|
      attributes package.
 | 
						|
 | 
						|
    :param key: string attribute name.
 | 
						|
 | 
						|
    :param passive: indicates loading behavior for the attribute
 | 
						|
       if the value is not already present.   This is a
 | 
						|
       bitflag attribute, which defaults to the symbol
 | 
						|
       :attr:`.PASSIVE_OFF` indicating all necessary SQL
 | 
						|
       should be emitted.
 | 
						|
 | 
						|
    .. seealso::
 | 
						|
 | 
						|
        :attr:`.AttributeState.history`
 | 
						|
 | 
						|
        :meth:`.AttributeState.load_history` - retrieve history
 | 
						|
        using loader callables if the value is not locally present.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    return get_state_history(instance_state(obj), key, passive)
 | 
						|
 | 
						|
 | 
						|
def get_state_history(state, key, passive=PASSIVE_OFF):
 | 
						|
    return state.get_history(key, passive)
 | 
						|
 | 
						|
 | 
						|
def has_parent(cls, obj, key, optimistic=False):
 | 
						|
    """TODO"""
 | 
						|
    manager = manager_of_class(cls)
 | 
						|
    state = instance_state(obj)
 | 
						|
    return manager.has_parent(state, key, optimistic)
 | 
						|
 | 
						|
 | 
						|
def register_attribute(class_, key, **kw):
 | 
						|
    comparator = kw.pop("comparator", None)
 | 
						|
    parententity = kw.pop("parententity", None)
 | 
						|
    doc = kw.pop("doc", None)
 | 
						|
    desc = register_descriptor(class_, key, comparator, parententity, doc=doc)
 | 
						|
    register_attribute_impl(class_, key, **kw)
 | 
						|
    return desc
 | 
						|
 | 
						|
 | 
						|
def register_attribute_impl(
 | 
						|
    class_,
 | 
						|
    key,
 | 
						|
    uselist=False,
 | 
						|
    callable_=None,
 | 
						|
    useobject=False,
 | 
						|
    impl_class=None,
 | 
						|
    backref=None,
 | 
						|
    **kw
 | 
						|
):
 | 
						|
 | 
						|
    manager = manager_of_class(class_)
 | 
						|
    if uselist:
 | 
						|
        factory = kw.pop("typecallable", None)
 | 
						|
        typecallable = manager.instrument_collection_class(
 | 
						|
            key, factory or list
 | 
						|
        )
 | 
						|
    else:
 | 
						|
        typecallable = kw.pop("typecallable", None)
 | 
						|
 | 
						|
    dispatch = manager[key].dispatch
 | 
						|
 | 
						|
    if impl_class:
 | 
						|
        impl = impl_class(class_, key, typecallable, dispatch, **kw)
 | 
						|
    elif uselist:
 | 
						|
        impl = CollectionAttributeImpl(
 | 
						|
            class_, key, callable_, dispatch, typecallable=typecallable, **kw
 | 
						|
        )
 | 
						|
    elif useobject:
 | 
						|
        impl = ScalarObjectAttributeImpl(
 | 
						|
            class_, key, callable_, dispatch, **kw
 | 
						|
        )
 | 
						|
    else:
 | 
						|
        impl = ScalarAttributeImpl(class_, key, callable_, dispatch, **kw)
 | 
						|
 | 
						|
    manager[key].impl = impl
 | 
						|
 | 
						|
    if backref:
 | 
						|
        backref_listeners(manager[key], backref, uselist)
 | 
						|
 | 
						|
    manager.post_configure_attribute(key)
 | 
						|
    return manager[key]
 | 
						|
 | 
						|
 | 
						|
def register_descriptor(
 | 
						|
    class_, key, comparator=None, parententity=None, doc=None
 | 
						|
):
 | 
						|
    manager = manager_of_class(class_)
 | 
						|
 | 
						|
    descriptor = InstrumentedAttribute(
 | 
						|
        class_, key, comparator=comparator, parententity=parententity
 | 
						|
    )
 | 
						|
 | 
						|
    descriptor.__doc__ = doc
 | 
						|
 | 
						|
    manager.instrument_attribute(key, descriptor)
 | 
						|
    return descriptor
 | 
						|
 | 
						|
 | 
						|
def unregister_attribute(class_, key):
 | 
						|
    manager_of_class(class_).uninstrument_attribute(key)
 | 
						|
 | 
						|
 | 
						|
def init_collection(obj, key):
 | 
						|
    """Initialize a collection attribute and return the collection adapter.
 | 
						|
 | 
						|
    This function is used to provide direct access to collection internals
 | 
						|
    for a previously unloaded attribute.  e.g.::
 | 
						|
 | 
						|
        collection_adapter = init_collection(someobject, 'elements')
 | 
						|
        for elem in values:
 | 
						|
            collection_adapter.append_without_event(elem)
 | 
						|
 | 
						|
    For an easier way to do the above, see
 | 
						|
    :func:`~sqlalchemy.orm.attributes.set_committed_value`.
 | 
						|
 | 
						|
    :param obj: a mapped object
 | 
						|
 | 
						|
    :param key: string attribute name where the collection is located.
 | 
						|
 | 
						|
    """
 | 
						|
    state = instance_state(obj)
 | 
						|
    dict_ = state.dict
 | 
						|
    return init_state_collection(state, dict_, key)
 | 
						|
 | 
						|
 | 
						|
def init_state_collection(state, dict_, key):
 | 
						|
    """Initialize a collection attribute and return the collection adapter.
 | 
						|
 | 
						|
    Discards any existing collection which may be there.
 | 
						|
 | 
						|
    """
 | 
						|
    attr = state.manager[key].impl
 | 
						|
 | 
						|
    old = dict_.pop(key, None)  # discard old collection
 | 
						|
    if old is not None:
 | 
						|
        old_collection = old._sa_adapter
 | 
						|
        attr._dispose_previous_collection(state, old, old_collection, False)
 | 
						|
 | 
						|
    user_data = attr._default_value(state, dict_)
 | 
						|
    adapter = attr.get_collection(state, dict_, user_data)
 | 
						|
    adapter._reset_empty()
 | 
						|
 | 
						|
    return adapter
 | 
						|
 | 
						|
 | 
						|
def set_committed_value(instance, key, value):
 | 
						|
    """Set the value of an attribute with no history events.
 | 
						|
 | 
						|
    Cancels any previous history present.  The value should be
 | 
						|
    a scalar value for scalar-holding attributes, or
 | 
						|
    an iterable for any collection-holding attribute.
 | 
						|
 | 
						|
    This is the same underlying method used when a lazy loader
 | 
						|
    fires off and loads additional data from the database.
 | 
						|
    In particular, this method can be used by application code
 | 
						|
    which has loaded additional attributes or collections through
 | 
						|
    separate queries, which can then be attached to an instance
 | 
						|
    as though it were part of its original loaded state.
 | 
						|
 | 
						|
    """
 | 
						|
    state, dict_ = instance_state(instance), instance_dict(instance)
 | 
						|
    state.manager[key].impl.set_committed_value(state, dict_, value)
 | 
						|
 | 
						|
 | 
						|
def set_attribute(instance, key, value, initiator=None):
 | 
						|
    """Set the value of an attribute, firing history events.
 | 
						|
 | 
						|
    This function may be used regardless of instrumentation
 | 
						|
    applied directly to the class, i.e. no descriptors are required.
 | 
						|
    Custom attribute management schemes will need to make usage
 | 
						|
    of this method to establish attribute state as understood
 | 
						|
    by SQLAlchemy.
 | 
						|
 | 
						|
    :param instance: the object that will be modified
 | 
						|
 | 
						|
    :param key: string name of the attribute
 | 
						|
 | 
						|
    :param value: value to assign
 | 
						|
 | 
						|
    :param initiator: an instance of :class:`.Event` that would have
 | 
						|
     been propagated from a previous event listener.  This argument
 | 
						|
     is used when the :func:`.set_attribute` function is being used within
 | 
						|
     an existing event listening function where an :class:`.Event` object
 | 
						|
     is being supplied; the object may be used to track the origin of the
 | 
						|
     chain of events.
 | 
						|
 | 
						|
     .. versionadded:: 1.2.3
 | 
						|
 | 
						|
    """
 | 
						|
    state, dict_ = instance_state(instance), instance_dict(instance)
 | 
						|
    state.manager[key].impl.set(state, dict_, value, initiator)
 | 
						|
 | 
						|
 | 
						|
def get_attribute(instance, key):
 | 
						|
    """Get the value of an attribute, firing any callables required.
 | 
						|
 | 
						|
    This function may be used regardless of instrumentation
 | 
						|
    applied directly to the class, i.e. no descriptors are required.
 | 
						|
    Custom attribute management schemes will need to make usage
 | 
						|
    of this method to make usage of attribute state as understood
 | 
						|
    by SQLAlchemy.
 | 
						|
 | 
						|
    """
 | 
						|
    state, dict_ = instance_state(instance), instance_dict(instance)
 | 
						|
    return state.manager[key].impl.get(state, dict_)
 | 
						|
 | 
						|
 | 
						|
def del_attribute(instance, key):
 | 
						|
    """Delete the value of an attribute, firing history events.
 | 
						|
 | 
						|
    This function may be used regardless of instrumentation
 | 
						|
    applied directly to the class, i.e. no descriptors are required.
 | 
						|
    Custom attribute management schemes will need to make usage
 | 
						|
    of this method to establish attribute state as understood
 | 
						|
    by SQLAlchemy.
 | 
						|
 | 
						|
    """
 | 
						|
    state, dict_ = instance_state(instance), instance_dict(instance)
 | 
						|
    state.manager[key].impl.delete(state, dict_)
 | 
						|
 | 
						|
 | 
						|
def flag_modified(instance, key):
 | 
						|
    """Mark an attribute on an instance as 'modified'.
 | 
						|
 | 
						|
    This sets the 'modified' flag on the instance and
 | 
						|
    establishes an unconditional change event for the given attribute.
 | 
						|
    The attribute must have a value present, else an
 | 
						|
    :class:`.InvalidRequestError` is raised.
 | 
						|
 | 
						|
    To mark an object "dirty" without referring to any specific attribute
 | 
						|
    so that it is considered within a flush, use the
 | 
						|
    :func:`.attributes.flag_dirty` call.
 | 
						|
 | 
						|
    .. seealso::
 | 
						|
 | 
						|
        :func:`.attributes.flag_dirty`
 | 
						|
 | 
						|
    """
 | 
						|
    state, dict_ = instance_state(instance), instance_dict(instance)
 | 
						|
    impl = state.manager[key].impl
 | 
						|
    impl.dispatch.modified(state, impl._modified_token)
 | 
						|
    state._modified_event(dict_, impl, NO_VALUE, is_userland=True)
 | 
						|
 | 
						|
 | 
						|
def flag_dirty(instance):
 | 
						|
    """Mark an instance as 'dirty' without any specific attribute mentioned.
 | 
						|
 | 
						|
    This is a special operation that will allow the object to travel through
 | 
						|
    the flush process for interception by events such as
 | 
						|
    :meth:`.SessionEvents.before_flush`.   Note that no SQL will be emitted in
 | 
						|
    the flush process for an object that has no changes, even if marked dirty
 | 
						|
    via this method.  However, a :meth:`.SessionEvents.before_flush` handler
 | 
						|
    will be able to see the object in the :attr:`.Session.dirty` collection and
 | 
						|
    may establish changes on it, which will then be included in the SQL
 | 
						|
    emitted.
 | 
						|
 | 
						|
    .. versionadded:: 1.2
 | 
						|
 | 
						|
    .. seealso::
 | 
						|
 | 
						|
        :func:`.attributes.flag_modified`
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    state, dict_ = instance_state(instance), instance_dict(instance)
 | 
						|
    state._modified_event(dict_, None, NO_VALUE, is_userland=True)
 |