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.
		
		
		
		
		
			
		
			
				
					
					
						
							1024 lines
						
					
					
						
							33 KiB
						
					
					
				
			
		
		
	
	
							1024 lines
						
					
					
						
							33 KiB
						
					
					
				# orm/state.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 of instances.
 | 
						|
 | 
						|
This module is usually not directly visible to user applications, but
 | 
						|
defines a large part of the ORM's interactivity.
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
import weakref
 | 
						|
 | 
						|
from . import base
 | 
						|
from . import exc as orm_exc
 | 
						|
from . import interfaces
 | 
						|
from .base import ATTR_WAS_SET
 | 
						|
from .base import INIT_OK
 | 
						|
from .base import NEVER_SET
 | 
						|
from .base import NO_VALUE
 | 
						|
from .base import PASSIVE_NO_INITIALIZE
 | 
						|
from .base import PASSIVE_NO_RESULT
 | 
						|
from .base import PASSIVE_OFF
 | 
						|
from .base import SQL_OK
 | 
						|
from .path_registry import PathRegistry
 | 
						|
from .. import exc as sa_exc
 | 
						|
from .. import inspection
 | 
						|
from .. import util
 | 
						|
 | 
						|
 | 
						|
# late-populated by session.py
 | 
						|
_sessions = None
 | 
						|
 | 
						|
# optionally late-provided by sqlalchemy.ext.asyncio.session
 | 
						|
_async_provider = None
 | 
						|
 | 
						|
 | 
						|
@inspection._self_inspects
 | 
						|
class InstanceState(interfaces.InspectionAttrInfo):
 | 
						|
    """tracks state information at the instance level.
 | 
						|
 | 
						|
    The :class:`.InstanceState` is a key object used by the
 | 
						|
    SQLAlchemy ORM in order to track the state of an object;
 | 
						|
    it is created the moment an object is instantiated, typically
 | 
						|
    as a result of :term:`instrumentation` which SQLAlchemy applies
 | 
						|
    to the ``__init__()`` method of the class.
 | 
						|
 | 
						|
    :class:`.InstanceState` is also a semi-public object,
 | 
						|
    available for runtime inspection as to the state of a
 | 
						|
    mapped instance, including information such as its current
 | 
						|
    status within a particular :class:`.Session` and details
 | 
						|
    about data on individual attributes.  The public API
 | 
						|
    in order to acquire a :class:`.InstanceState` object
 | 
						|
    is to use the :func:`_sa.inspect` system::
 | 
						|
 | 
						|
        >>> from sqlalchemy import inspect
 | 
						|
        >>> insp = inspect(some_mapped_object)
 | 
						|
 | 
						|
    .. seealso::
 | 
						|
 | 
						|
        :ref:`core_inspection_toplevel`
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    session_id = None
 | 
						|
    key = None
 | 
						|
    runid = None
 | 
						|
    load_options = util.EMPTY_SET
 | 
						|
    load_path = PathRegistry.root
 | 
						|
    insert_order = None
 | 
						|
    _strong_obj = None
 | 
						|
    modified = False
 | 
						|
    expired = False
 | 
						|
    _deleted = False
 | 
						|
    _load_pending = False
 | 
						|
    _orphaned_outside_of_session = False
 | 
						|
    is_instance = True
 | 
						|
    identity_token = None
 | 
						|
    _last_known_values = ()
 | 
						|
 | 
						|
    callables = ()
 | 
						|
    """A namespace where a per-state loader callable can be associated.
 | 
						|
 | 
						|
    In SQLAlchemy 1.0, this is only used for lazy loaders / deferred
 | 
						|
    loaders that were set up via query option.
 | 
						|
 | 
						|
    Previously, callables was used also to indicate expired attributes
 | 
						|
    by storing a link to the InstanceState itself in this dictionary.
 | 
						|
    This role is now handled by the expired_attributes set.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, obj, manager):
 | 
						|
        self.class_ = obj.__class__
 | 
						|
        self.manager = manager
 | 
						|
        self.obj = weakref.ref(obj, self._cleanup)
 | 
						|
        self.committed_state = {}
 | 
						|
        self.expired_attributes = set()
 | 
						|
 | 
						|
    expired_attributes = None
 | 
						|
    """The set of keys which are 'expired' to be loaded by
 | 
						|
       the manager's deferred scalar loader, assuming no pending
 | 
						|
       changes.
 | 
						|
 | 
						|
       see also the ``unmodified`` collection which is intersected
 | 
						|
       against this set when a refresh operation occurs."""
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def attrs(self):
 | 
						|
        """Return a namespace representing each attribute on
 | 
						|
        the mapped object, including its current value
 | 
						|
        and history.
 | 
						|
 | 
						|
        The returned object is an instance of :class:`.AttributeState`.
 | 
						|
        This object allows inspection of the current data
 | 
						|
        within an attribute as well as attribute history
 | 
						|
        since the last flush.
 | 
						|
 | 
						|
        """
 | 
						|
        return util.ImmutableProperties(
 | 
						|
            dict((key, AttributeState(self, key)) for key in self.manager)
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def transient(self):
 | 
						|
        """Return ``True`` if the object is :term:`transient`.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`session_object_states`
 | 
						|
 | 
						|
        """
 | 
						|
        return self.key is None and not self._attached
 | 
						|
 | 
						|
    @property
 | 
						|
    def pending(self):
 | 
						|
        """Return ``True`` if the object is :term:`pending`.
 | 
						|
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`session_object_states`
 | 
						|
 | 
						|
        """
 | 
						|
        return self.key is None and self._attached
 | 
						|
 | 
						|
    @property
 | 
						|
    def deleted(self):
 | 
						|
        """Return ``True`` if the object is :term:`deleted`.
 | 
						|
 | 
						|
        An object that is in the deleted state is guaranteed to
 | 
						|
        not be within the :attr:`.Session.identity_map` of its parent
 | 
						|
        :class:`.Session`; however if the session's transaction is rolled
 | 
						|
        back, the object will be restored to the persistent state and
 | 
						|
        the identity map.
 | 
						|
 | 
						|
        .. note::
 | 
						|
 | 
						|
            The :attr:`.InstanceState.deleted` attribute refers to a specific
 | 
						|
            state of the object that occurs between the "persistent" and
 | 
						|
            "detached" states; once the object is :term:`detached`, the
 | 
						|
            :attr:`.InstanceState.deleted` attribute **no longer returns
 | 
						|
            True**; in order to detect that a state was deleted, regardless
 | 
						|
            of whether or not the object is associated with a
 | 
						|
            :class:`.Session`, use the :attr:`.InstanceState.was_deleted`
 | 
						|
            accessor.
 | 
						|
 | 
						|
        .. versionadded: 1.1
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`session_object_states`
 | 
						|
 | 
						|
        """
 | 
						|
        return self.key is not None and self._attached and self._deleted
 | 
						|
 | 
						|
    @property
 | 
						|
    def was_deleted(self):
 | 
						|
        """Return True if this object is or was previously in the
 | 
						|
        "deleted" state and has not been reverted to persistent.
 | 
						|
 | 
						|
        This flag returns True once the object was deleted in flush.
 | 
						|
        When the object is expunged from the session either explicitly
 | 
						|
        or via transaction commit and enters the "detached" state,
 | 
						|
        this flag will continue to report True.
 | 
						|
 | 
						|
        .. versionadded:: 1.1 - added a local method form of
 | 
						|
           :func:`.orm.util.was_deleted`.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :attr:`.InstanceState.deleted` - refers to the "deleted" state
 | 
						|
 | 
						|
            :func:`.orm.util.was_deleted` - standalone function
 | 
						|
 | 
						|
            :ref:`session_object_states`
 | 
						|
 | 
						|
        """
 | 
						|
        return self._deleted
 | 
						|
 | 
						|
    @property
 | 
						|
    def persistent(self):
 | 
						|
        """Return ``True`` if the object is :term:`persistent`.
 | 
						|
 | 
						|
        An object that is in the persistent state is guaranteed to
 | 
						|
        be within the :attr:`.Session.identity_map` of its parent
 | 
						|
        :class:`.Session`.
 | 
						|
 | 
						|
        .. versionchanged:: 1.1 The :attr:`.InstanceState.persistent`
 | 
						|
           accessor no longer returns True for an object that was
 | 
						|
           "deleted" within a flush; use the :attr:`.InstanceState.deleted`
 | 
						|
           accessor to detect this state.   This allows the "persistent"
 | 
						|
           state to guarantee membership in the identity map.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`session_object_states`
 | 
						|
 | 
						|
        """
 | 
						|
        return self.key is not None and self._attached and not self._deleted
 | 
						|
 | 
						|
    @property
 | 
						|
    def detached(self):
 | 
						|
        """Return ``True`` if the object is :term:`detached`.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`session_object_states`
 | 
						|
 | 
						|
        """
 | 
						|
        return self.key is not None and not self._attached
 | 
						|
 | 
						|
    @property
 | 
						|
    @util.preload_module("sqlalchemy.orm.session")
 | 
						|
    def _attached(self):
 | 
						|
        return (
 | 
						|
            self.session_id is not None
 | 
						|
            and self.session_id in util.preloaded.orm_session._sessions
 | 
						|
        )
 | 
						|
 | 
						|
    def _track_last_known_value(self, key):
 | 
						|
        """Track the last known value of a particular key after expiration
 | 
						|
        operations.
 | 
						|
 | 
						|
        .. versionadded:: 1.3
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        if key not in self._last_known_values:
 | 
						|
            self._last_known_values = dict(self._last_known_values)
 | 
						|
            self._last_known_values[key] = NO_VALUE
 | 
						|
 | 
						|
    @property
 | 
						|
    def session(self):
 | 
						|
        """Return the owning :class:`.Session` for this instance,
 | 
						|
        or ``None`` if none available.
 | 
						|
 | 
						|
        Note that the result here can in some cases be *different*
 | 
						|
        from that of ``obj in session``; an object that's been deleted
 | 
						|
        will report as not ``in session``, however if the transaction is
 | 
						|
        still in progress, this attribute will still refer to that session.
 | 
						|
        Only when the transaction is completed does the object become
 | 
						|
        fully detached under normal circumstances.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :attr:`_orm.InstanceState.async_session`
 | 
						|
 | 
						|
        """
 | 
						|
        if self.session_id:
 | 
						|
            try:
 | 
						|
                return _sessions[self.session_id]
 | 
						|
            except KeyError:
 | 
						|
                pass
 | 
						|
        return None
 | 
						|
 | 
						|
    @property
 | 
						|
    def async_session(self):
 | 
						|
        """Return the owning :class:`_asyncio.AsyncSession` for this instance,
 | 
						|
        or ``None`` if none available.
 | 
						|
 | 
						|
        This attribute is only non-None when the :mod:`sqlalchemy.ext.asyncio`
 | 
						|
        API is in use for this ORM object. The returned
 | 
						|
        :class:`_asyncio.AsyncSession` object will be a proxy for the
 | 
						|
        :class:`_orm.Session` object that would be returned from the
 | 
						|
        :attr:`_orm.InstanceState.session` attribute for this
 | 
						|
        :class:`_orm.InstanceState`.
 | 
						|
 | 
						|
        .. versionadded:: 1.4.18
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`asyncio_toplevel`
 | 
						|
 | 
						|
        """
 | 
						|
        if _async_provider is None:
 | 
						|
            return None
 | 
						|
 | 
						|
        sess = self.session
 | 
						|
        if sess is not None:
 | 
						|
            return _async_provider(sess)
 | 
						|
        else:
 | 
						|
            return None
 | 
						|
 | 
						|
    @property
 | 
						|
    def object(self):
 | 
						|
        """Return the mapped object represented by this
 | 
						|
        :class:`.InstanceState`."""
 | 
						|
        return self.obj()
 | 
						|
 | 
						|
    @property
 | 
						|
    def identity(self):
 | 
						|
        """Return the mapped identity of the mapped object.
 | 
						|
        This is the primary key identity as persisted by the ORM
 | 
						|
        which can always be passed directly to
 | 
						|
        :meth:`_query.Query.get`.
 | 
						|
 | 
						|
        Returns ``None`` if the object has no primary key identity.
 | 
						|
 | 
						|
        .. note::
 | 
						|
            An object which is :term:`transient` or :term:`pending`
 | 
						|
            does **not** have a mapped identity until it is flushed,
 | 
						|
            even if its attributes include primary key values.
 | 
						|
 | 
						|
        """
 | 
						|
        if self.key is None:
 | 
						|
            return None
 | 
						|
        else:
 | 
						|
            return self.key[1]
 | 
						|
 | 
						|
    @property
 | 
						|
    def identity_key(self):
 | 
						|
        """Return the identity key for the mapped object.
 | 
						|
 | 
						|
        This is the key used to locate the object within
 | 
						|
        the :attr:`.Session.identity_map` mapping.   It contains
 | 
						|
        the identity as returned by :attr:`.identity` within it.
 | 
						|
 | 
						|
 | 
						|
        """
 | 
						|
        # TODO: just change .key to .identity_key across
 | 
						|
        # the board ?  probably
 | 
						|
        return self.key
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def parents(self):
 | 
						|
        return {}
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def _pending_mutations(self):
 | 
						|
        return {}
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def _empty_collections(self):
 | 
						|
        return {}
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def mapper(self):
 | 
						|
        """Return the :class:`_orm.Mapper` used for this mapped object."""
 | 
						|
        return self.manager.mapper
 | 
						|
 | 
						|
    @property
 | 
						|
    def has_identity(self):
 | 
						|
        """Return ``True`` if this object has an identity key.
 | 
						|
 | 
						|
        This should always have the same value as the
 | 
						|
        expression ``state.persistent`` or ``state.detached``.
 | 
						|
 | 
						|
        """
 | 
						|
        return bool(self.key)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _detach_states(self, states, session, to_transient=False):
 | 
						|
        persistent_to_detached = (
 | 
						|
            session.dispatch.persistent_to_detached or None
 | 
						|
        )
 | 
						|
        deleted_to_detached = session.dispatch.deleted_to_detached or None
 | 
						|
        pending_to_transient = session.dispatch.pending_to_transient or None
 | 
						|
        persistent_to_transient = (
 | 
						|
            session.dispatch.persistent_to_transient or None
 | 
						|
        )
 | 
						|
 | 
						|
        for state in states:
 | 
						|
            deleted = state._deleted
 | 
						|
            pending = state.key is None
 | 
						|
            persistent = not pending and not deleted
 | 
						|
 | 
						|
            state.session_id = None
 | 
						|
 | 
						|
            if to_transient and state.key:
 | 
						|
                del state.key
 | 
						|
            if persistent:
 | 
						|
                if to_transient:
 | 
						|
                    if persistent_to_transient is not None:
 | 
						|
                        persistent_to_transient(session, state)
 | 
						|
                elif persistent_to_detached is not None:
 | 
						|
                    persistent_to_detached(session, state)
 | 
						|
            elif deleted and deleted_to_detached is not None:
 | 
						|
                deleted_to_detached(session, state)
 | 
						|
            elif pending and pending_to_transient is not None:
 | 
						|
                pending_to_transient(session, state)
 | 
						|
 | 
						|
            state._strong_obj = None
 | 
						|
 | 
						|
    def _detach(self, session=None):
 | 
						|
        if session:
 | 
						|
            InstanceState._detach_states([self], session)
 | 
						|
        else:
 | 
						|
            self.session_id = self._strong_obj = None
 | 
						|
 | 
						|
    def _dispose(self):
 | 
						|
        self._detach()
 | 
						|
        del self.obj
 | 
						|
 | 
						|
    def _cleanup(self, ref):
 | 
						|
        """Weakref callback cleanup.
 | 
						|
 | 
						|
        This callable cleans out the state when it is being garbage
 | 
						|
        collected.
 | 
						|
 | 
						|
        this _cleanup **assumes** that there are no strong refs to us!
 | 
						|
        Will not work otherwise!
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        # Python builtins become undefined during interpreter shutdown.
 | 
						|
        # Guard against exceptions during this phase, as the method cannot
 | 
						|
        # proceed in any case if builtins have been undefined.
 | 
						|
        if dict is None:
 | 
						|
            return
 | 
						|
 | 
						|
        instance_dict = self._instance_dict()
 | 
						|
        if instance_dict is not None:
 | 
						|
            instance_dict._fast_discard(self)
 | 
						|
            del self._instance_dict
 | 
						|
 | 
						|
            # we can't possibly be in instance_dict._modified
 | 
						|
            # b.c. this is weakref cleanup only, that set
 | 
						|
            # is strong referencing!
 | 
						|
            # assert self not in instance_dict._modified
 | 
						|
 | 
						|
        self.session_id = self._strong_obj = None
 | 
						|
        del self.obj
 | 
						|
 | 
						|
    def obj(self):
 | 
						|
        return None
 | 
						|
 | 
						|
    @property
 | 
						|
    def dict(self):
 | 
						|
        """Return the instance dict used by the object.
 | 
						|
 | 
						|
        Under normal circumstances, this is always synonymous
 | 
						|
        with the ``__dict__`` attribute of the mapped object,
 | 
						|
        unless an alternative instrumentation system has been
 | 
						|
        configured.
 | 
						|
 | 
						|
        In the case that the actual object has been garbage
 | 
						|
        collected, this accessor returns a blank dictionary.
 | 
						|
 | 
						|
        """
 | 
						|
        o = self.obj()
 | 
						|
        if o is not None:
 | 
						|
            return base.instance_dict(o)
 | 
						|
        else:
 | 
						|
            return {}
 | 
						|
 | 
						|
    def _initialize_instance(*mixed, **kwargs):
 | 
						|
        self, instance, args = mixed[0], mixed[1], mixed[2:]  # noqa
 | 
						|
        manager = self.manager
 | 
						|
 | 
						|
        manager.dispatch.init(self, args, kwargs)
 | 
						|
 | 
						|
        try:
 | 
						|
            return manager.original_init(*mixed[1:], **kwargs)
 | 
						|
        except:
 | 
						|
            with util.safe_reraise():
 | 
						|
                manager.dispatch.init_failure(self, args, kwargs)
 | 
						|
 | 
						|
    def get_history(self, key, passive):
 | 
						|
        return self.manager[key].impl.get_history(self, self.dict, passive)
 | 
						|
 | 
						|
    def get_impl(self, key):
 | 
						|
        return self.manager[key].impl
 | 
						|
 | 
						|
    def _get_pending_mutation(self, key):
 | 
						|
        if key not in self._pending_mutations:
 | 
						|
            self._pending_mutations[key] = PendingCollection()
 | 
						|
        return self._pending_mutations[key]
 | 
						|
 | 
						|
    def __getstate__(self):
 | 
						|
        state_dict = {"instance": self.obj()}
 | 
						|
        state_dict.update(
 | 
						|
            (k, self.__dict__[k])
 | 
						|
            for k in (
 | 
						|
                "committed_state",
 | 
						|
                "_pending_mutations",
 | 
						|
                "modified",
 | 
						|
                "expired",
 | 
						|
                "callables",
 | 
						|
                "key",
 | 
						|
                "parents",
 | 
						|
                "load_options",
 | 
						|
                "class_",
 | 
						|
                "expired_attributes",
 | 
						|
                "info",
 | 
						|
            )
 | 
						|
            if k in self.__dict__
 | 
						|
        )
 | 
						|
        if self.load_path:
 | 
						|
            state_dict["load_path"] = self.load_path.serialize()
 | 
						|
 | 
						|
        state_dict["manager"] = self.manager._serialize(self, state_dict)
 | 
						|
 | 
						|
        return state_dict
 | 
						|
 | 
						|
    def __setstate__(self, state_dict):
 | 
						|
        inst = state_dict["instance"]
 | 
						|
        if inst is not None:
 | 
						|
            self.obj = weakref.ref(inst, self._cleanup)
 | 
						|
            self.class_ = inst.__class__
 | 
						|
        else:
 | 
						|
            # None being possible here generally new as of 0.7.4
 | 
						|
            # due to storage of state in "parents".  "class_"
 | 
						|
            # also new.
 | 
						|
            self.obj = None
 | 
						|
            self.class_ = state_dict["class_"]
 | 
						|
 | 
						|
        self.committed_state = state_dict.get("committed_state", {})
 | 
						|
        self._pending_mutations = state_dict.get("_pending_mutations", {})
 | 
						|
        self.parents = state_dict.get("parents", {})
 | 
						|
        self.modified = state_dict.get("modified", False)
 | 
						|
        self.expired = state_dict.get("expired", False)
 | 
						|
        if "info" in state_dict:
 | 
						|
            self.info.update(state_dict["info"])
 | 
						|
        if "callables" in state_dict:
 | 
						|
            self.callables = state_dict["callables"]
 | 
						|
 | 
						|
            try:
 | 
						|
                self.expired_attributes = state_dict["expired_attributes"]
 | 
						|
            except KeyError:
 | 
						|
                self.expired_attributes = set()
 | 
						|
                # 0.9 and earlier compat
 | 
						|
                for k in list(self.callables):
 | 
						|
                    if self.callables[k] is self:
 | 
						|
                        self.expired_attributes.add(k)
 | 
						|
                        del self.callables[k]
 | 
						|
        else:
 | 
						|
            if "expired_attributes" in state_dict:
 | 
						|
                self.expired_attributes = state_dict["expired_attributes"]
 | 
						|
            else:
 | 
						|
                self.expired_attributes = set()
 | 
						|
 | 
						|
        self.__dict__.update(
 | 
						|
            [
 | 
						|
                (k, state_dict[k])
 | 
						|
                for k in ("key", "load_options")
 | 
						|
                if k in state_dict
 | 
						|
            ]
 | 
						|
        )
 | 
						|
        if self.key:
 | 
						|
            try:
 | 
						|
                self.identity_token = self.key[2]
 | 
						|
            except IndexError:
 | 
						|
                # 1.1 and earlier compat before identity_token
 | 
						|
                assert len(self.key) == 2
 | 
						|
                self.key = self.key + (None,)
 | 
						|
                self.identity_token = None
 | 
						|
 | 
						|
        if "load_path" in state_dict:
 | 
						|
            self.load_path = PathRegistry.deserialize(state_dict["load_path"])
 | 
						|
 | 
						|
        state_dict["manager"](self, inst, state_dict)
 | 
						|
 | 
						|
    def _reset(self, dict_, key):
 | 
						|
        """Remove the given attribute and any
 | 
						|
        callables associated with it."""
 | 
						|
 | 
						|
        old = dict_.pop(key, None)
 | 
						|
        if old is not None and self.manager[key].impl.collection:
 | 
						|
            self.manager[key].impl._invalidate_collection(old)
 | 
						|
        self.expired_attributes.discard(key)
 | 
						|
        if self.callables:
 | 
						|
            self.callables.pop(key, None)
 | 
						|
 | 
						|
    def _copy_callables(self, from_):
 | 
						|
        if "callables" in from_.__dict__:
 | 
						|
            self.callables = dict(from_.callables)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _instance_level_callable_processor(cls, manager, fn, key):
 | 
						|
        impl = manager[key].impl
 | 
						|
        if impl.collection:
 | 
						|
 | 
						|
            def _set_callable(state, dict_, row):
 | 
						|
                if "callables" not in state.__dict__:
 | 
						|
                    state.callables = {}
 | 
						|
                old = dict_.pop(key, None)
 | 
						|
                if old is not None:
 | 
						|
                    impl._invalidate_collection(old)
 | 
						|
                state.callables[key] = fn
 | 
						|
 | 
						|
        else:
 | 
						|
 | 
						|
            def _set_callable(state, dict_, row):
 | 
						|
                if "callables" not in state.__dict__:
 | 
						|
                    state.callables = {}
 | 
						|
                state.callables[key] = fn
 | 
						|
 | 
						|
        return _set_callable
 | 
						|
 | 
						|
    def _expire(self, dict_, modified_set):
 | 
						|
        self.expired = True
 | 
						|
        if self.modified:
 | 
						|
            modified_set.discard(self)
 | 
						|
            self.committed_state.clear()
 | 
						|
            self.modified = False
 | 
						|
 | 
						|
        self._strong_obj = None
 | 
						|
 | 
						|
        if "_pending_mutations" in self.__dict__:
 | 
						|
            del self.__dict__["_pending_mutations"]
 | 
						|
 | 
						|
        if "parents" in self.__dict__:
 | 
						|
            del self.__dict__["parents"]
 | 
						|
 | 
						|
        self.expired_attributes.update(
 | 
						|
            [impl.key for impl in self.manager._loader_impls]
 | 
						|
        )
 | 
						|
 | 
						|
        if self.callables:
 | 
						|
            # the per state loader callables we can remove here are
 | 
						|
            # LoadDeferredColumns, which undefers a column at the instance
 | 
						|
            # level that is mapped with deferred, and LoadLazyAttribute,
 | 
						|
            # which lazy loads a relationship at the instance level that
 | 
						|
            # is mapped with "noload" or perhaps "immediateload".
 | 
						|
            # Before 1.4, only column-based
 | 
						|
            # attributes could be considered to be "expired", so here they
 | 
						|
            # were the only ones "unexpired", which means to make them deferred
 | 
						|
            # again.   For the moment, as of 1.4 we also apply the same
 | 
						|
            # treatment relationships now, that is, an instance level lazy
 | 
						|
            # loader is reset in the same way as a column loader.
 | 
						|
            for k in self.expired_attributes.intersection(self.callables):
 | 
						|
                del self.callables[k]
 | 
						|
 | 
						|
        for k in self.manager._collection_impl_keys.intersection(dict_):
 | 
						|
            collection = dict_.pop(k)
 | 
						|
            collection._sa_adapter.invalidated = True
 | 
						|
 | 
						|
        if self._last_known_values:
 | 
						|
            self._last_known_values.update(
 | 
						|
                (k, dict_[k]) for k in self._last_known_values if k in dict_
 | 
						|
            )
 | 
						|
 | 
						|
        for key in self.manager._all_key_set.intersection(dict_):
 | 
						|
            del dict_[key]
 | 
						|
 | 
						|
        self.manager.dispatch.expire(self, None)
 | 
						|
 | 
						|
    def _expire_attributes(self, dict_, attribute_names, no_loader=False):
 | 
						|
        pending = self.__dict__.get("_pending_mutations", None)
 | 
						|
 | 
						|
        callables = self.callables
 | 
						|
 | 
						|
        for key in attribute_names:
 | 
						|
            impl = self.manager[key].impl
 | 
						|
            if impl.accepts_scalar_loader:
 | 
						|
                if no_loader and (impl.callable_ or key in callables):
 | 
						|
                    continue
 | 
						|
 | 
						|
                self.expired_attributes.add(key)
 | 
						|
                if callables and key in callables:
 | 
						|
                    del callables[key]
 | 
						|
            old = dict_.pop(key, NO_VALUE)
 | 
						|
            if impl.collection and old is not NO_VALUE:
 | 
						|
                impl._invalidate_collection(old)
 | 
						|
 | 
						|
            if (
 | 
						|
                self._last_known_values
 | 
						|
                and key in self._last_known_values
 | 
						|
                and old is not NO_VALUE
 | 
						|
            ):
 | 
						|
                self._last_known_values[key] = old
 | 
						|
 | 
						|
            self.committed_state.pop(key, None)
 | 
						|
            if pending:
 | 
						|
                pending.pop(key, None)
 | 
						|
 | 
						|
        self.manager.dispatch.expire(self, attribute_names)
 | 
						|
 | 
						|
    def _load_expired(self, state, passive):
 | 
						|
        """__call__ allows the InstanceState to act as a deferred
 | 
						|
        callable for loading expired attributes, which is also
 | 
						|
        serializable (picklable).
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        if not passive & SQL_OK:
 | 
						|
            return PASSIVE_NO_RESULT
 | 
						|
 | 
						|
        toload = self.expired_attributes.intersection(self.unmodified)
 | 
						|
        toload = toload.difference(
 | 
						|
            attr
 | 
						|
            for attr in toload
 | 
						|
            if not self.manager[attr].impl.load_on_unexpire
 | 
						|
        )
 | 
						|
 | 
						|
        self.manager.expired_attribute_loader(self, toload, passive)
 | 
						|
 | 
						|
        # if the loader failed, or this
 | 
						|
        # instance state didn't have an identity,
 | 
						|
        # the attributes still might be in the callables
 | 
						|
        # dict.  ensure they are removed.
 | 
						|
        self.expired_attributes.clear()
 | 
						|
 | 
						|
        return ATTR_WAS_SET
 | 
						|
 | 
						|
    @property
 | 
						|
    def unmodified(self):
 | 
						|
        """Return the set of keys which have no uncommitted changes"""
 | 
						|
 | 
						|
        return set(self.manager).difference(self.committed_state)
 | 
						|
 | 
						|
    def unmodified_intersection(self, keys):
 | 
						|
        """Return self.unmodified.intersection(keys)."""
 | 
						|
 | 
						|
        return (
 | 
						|
            set(keys)
 | 
						|
            .intersection(self.manager)
 | 
						|
            .difference(self.committed_state)
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def unloaded(self):
 | 
						|
        """Return the set of keys which do not have a loaded value.
 | 
						|
 | 
						|
        This includes expired attributes and any other attribute that
 | 
						|
        was never populated or modified.
 | 
						|
 | 
						|
        """
 | 
						|
        return (
 | 
						|
            set(self.manager)
 | 
						|
            .difference(self.committed_state)
 | 
						|
            .difference(self.dict)
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def unloaded_expirable(self):
 | 
						|
        """Return the set of keys which do not have a loaded value.
 | 
						|
 | 
						|
        This includes expired attributes and any other attribute that
 | 
						|
        was never populated or modified.
 | 
						|
 | 
						|
        """
 | 
						|
        return self.unloaded
 | 
						|
 | 
						|
    @property
 | 
						|
    def _unloaded_non_object(self):
 | 
						|
        return self.unloaded.intersection(
 | 
						|
            attr
 | 
						|
            for attr in self.manager
 | 
						|
            if self.manager[attr].impl.accepts_scalar_loader
 | 
						|
        )
 | 
						|
 | 
						|
    def _instance_dict(self):
 | 
						|
        return None
 | 
						|
 | 
						|
    def _modified_event(
 | 
						|
        self, dict_, attr, previous, collection=False, is_userland=False
 | 
						|
    ):
 | 
						|
        if attr:
 | 
						|
            if not attr.send_modified_events:
 | 
						|
                return
 | 
						|
            if is_userland and attr.key not in dict_:
 | 
						|
                raise sa_exc.InvalidRequestError(
 | 
						|
                    "Can't flag attribute '%s' modified; it's not present in "
 | 
						|
                    "the object state" % attr.key
 | 
						|
                )
 | 
						|
            if attr.key not in self.committed_state or is_userland:
 | 
						|
                if collection:
 | 
						|
                    if previous is NEVER_SET:
 | 
						|
                        if attr.key in dict_:
 | 
						|
                            previous = dict_[attr.key]
 | 
						|
 | 
						|
                    if previous not in (None, NO_VALUE, NEVER_SET):
 | 
						|
                        previous = attr.copy(previous)
 | 
						|
                self.committed_state[attr.key] = previous
 | 
						|
 | 
						|
            if attr.key in self._last_known_values:
 | 
						|
                self._last_known_values[attr.key] = NO_VALUE
 | 
						|
 | 
						|
        # assert self._strong_obj is None or self.modified
 | 
						|
 | 
						|
        if (self.session_id and self._strong_obj is None) or not self.modified:
 | 
						|
            self.modified = True
 | 
						|
            instance_dict = self._instance_dict()
 | 
						|
            if instance_dict:
 | 
						|
                has_modified = bool(instance_dict._modified)
 | 
						|
                instance_dict._modified.add(self)
 | 
						|
            else:
 | 
						|
                has_modified = False
 | 
						|
 | 
						|
            # only create _strong_obj link if attached
 | 
						|
            # to a session
 | 
						|
 | 
						|
            inst = self.obj()
 | 
						|
            if self.session_id:
 | 
						|
                self._strong_obj = inst
 | 
						|
 | 
						|
                # if identity map already had modified objects,
 | 
						|
                # assume autobegin already occurred, else check
 | 
						|
                # for autobegin
 | 
						|
                if not has_modified:
 | 
						|
                    # inline of autobegin, to ensure session transaction
 | 
						|
                    # snapshot is established
 | 
						|
                    try:
 | 
						|
                        session = _sessions[self.session_id]
 | 
						|
                    except KeyError:
 | 
						|
                        pass
 | 
						|
                    else:
 | 
						|
                        if session._transaction is None:
 | 
						|
                            session._autobegin()
 | 
						|
 | 
						|
            if inst is None and attr:
 | 
						|
                raise orm_exc.ObjectDereferencedError(
 | 
						|
                    "Can't emit change event for attribute '%s' - "
 | 
						|
                    "parent object of type %s has been garbage "
 | 
						|
                    "collected."
 | 
						|
                    % (self.manager[attr.key], base.state_class_str(self))
 | 
						|
                )
 | 
						|
 | 
						|
    def _commit(self, dict_, keys):
 | 
						|
        """Commit attributes.
 | 
						|
 | 
						|
        This is used by a partial-attribute load operation to mark committed
 | 
						|
        those attributes which were refreshed from the database.
 | 
						|
 | 
						|
        Attributes marked as "expired" can potentially remain "expired" after
 | 
						|
        this step if a value was not populated in state.dict.
 | 
						|
 | 
						|
        """
 | 
						|
        for key in keys:
 | 
						|
            self.committed_state.pop(key, None)
 | 
						|
 | 
						|
        self.expired = False
 | 
						|
 | 
						|
        self.expired_attributes.difference_update(
 | 
						|
            set(keys).intersection(dict_)
 | 
						|
        )
 | 
						|
 | 
						|
        # the per-keys commit removes object-level callables,
 | 
						|
        # while that of commit_all does not.  it's not clear
 | 
						|
        # if this behavior has a clear rationale, however tests do
 | 
						|
        # ensure this is what it does.
 | 
						|
        if self.callables:
 | 
						|
            for key in (
 | 
						|
                set(self.callables).intersection(keys).intersection(dict_)
 | 
						|
            ):
 | 
						|
                del self.callables[key]
 | 
						|
 | 
						|
    def _commit_all(self, dict_, instance_dict=None):
 | 
						|
        """commit all attributes unconditionally.
 | 
						|
 | 
						|
        This is used after a flush() or a full load/refresh
 | 
						|
        to remove all pending state from the instance.
 | 
						|
 | 
						|
         - all attributes are marked as "committed"
 | 
						|
         - the "strong dirty reference" is removed
 | 
						|
         - the "modified" flag is set to False
 | 
						|
         - any "expired" markers for scalar attributes loaded are removed.
 | 
						|
         - lazy load callables for objects / collections *stay*
 | 
						|
 | 
						|
        Attributes marked as "expired" can potentially remain
 | 
						|
        "expired" after this step if a value was not populated in state.dict.
 | 
						|
 | 
						|
        """
 | 
						|
        self._commit_all_states([(self, dict_)], instance_dict)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _commit_all_states(self, iter_, instance_dict=None):
 | 
						|
        """Mass / highly inlined version of commit_all()."""
 | 
						|
 | 
						|
        for state, dict_ in iter_:
 | 
						|
            state_dict = state.__dict__
 | 
						|
 | 
						|
            state.committed_state.clear()
 | 
						|
 | 
						|
            if "_pending_mutations" in state_dict:
 | 
						|
                del state_dict["_pending_mutations"]
 | 
						|
 | 
						|
            state.expired_attributes.difference_update(dict_)
 | 
						|
 | 
						|
            if instance_dict and state.modified:
 | 
						|
                instance_dict._modified.discard(state)
 | 
						|
 | 
						|
            state.modified = state.expired = False
 | 
						|
            state._strong_obj = None
 | 
						|
 | 
						|
 | 
						|
class AttributeState(object):
 | 
						|
    """Provide an inspection interface corresponding
 | 
						|
    to a particular attribute on a particular mapped object.
 | 
						|
 | 
						|
    The :class:`.AttributeState` object is accessed
 | 
						|
    via the :attr:`.InstanceState.attrs` collection
 | 
						|
    of a particular :class:`.InstanceState`::
 | 
						|
 | 
						|
        from sqlalchemy import inspect
 | 
						|
 | 
						|
        insp = inspect(some_mapped_object)
 | 
						|
        attr_state = insp.attrs.some_attribute
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, state, key):
 | 
						|
        self.state = state
 | 
						|
        self.key = key
 | 
						|
 | 
						|
    @property
 | 
						|
    def loaded_value(self):
 | 
						|
        """The current value of this attribute as loaded from the database.
 | 
						|
 | 
						|
        If the value has not been loaded, or is otherwise not present
 | 
						|
        in the object's dictionary, returns NO_VALUE.
 | 
						|
 | 
						|
        """
 | 
						|
        return self.state.dict.get(self.key, NO_VALUE)
 | 
						|
 | 
						|
    @property
 | 
						|
    def value(self):
 | 
						|
        """Return the value of this attribute.
 | 
						|
 | 
						|
        This operation is equivalent to accessing the object's
 | 
						|
        attribute directly or via ``getattr()``, and will fire
 | 
						|
        off any pending loader callables if needed.
 | 
						|
 | 
						|
        """
 | 
						|
        return self.state.manager[self.key].__get__(
 | 
						|
            self.state.obj(), self.state.class_
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def history(self):
 | 
						|
        """Return the current **pre-flush** change history for
 | 
						|
        this attribute, via the :class:`.History` interface.
 | 
						|
 | 
						|
        This method will **not** emit loader callables if the value of the
 | 
						|
        attribute is unloaded.
 | 
						|
 | 
						|
        .. note::
 | 
						|
 | 
						|
            The attribute history system tracks changes on a **per flush
 | 
						|
            basis**. Each time the :class:`.Session` is flushed, the history
 | 
						|
            of each attribute is reset to empty.   The :class:`.Session` by
 | 
						|
            default autoflushes each time a :class:`_query.Query` is invoked.
 | 
						|
            For
 | 
						|
            options on how to control this, see :ref:`session_flushing`.
 | 
						|
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :meth:`.AttributeState.load_history` - retrieve history
 | 
						|
            using loader callables if the value is not locally present.
 | 
						|
 | 
						|
            :func:`.attributes.get_history` - underlying function
 | 
						|
 | 
						|
        """
 | 
						|
        return self.state.get_history(self.key, PASSIVE_NO_INITIALIZE)
 | 
						|
 | 
						|
    def load_history(self):
 | 
						|
        """Return the current **pre-flush** change history for
 | 
						|
        this attribute, via the :class:`.History` interface.
 | 
						|
 | 
						|
        This method **will** emit loader callables if the value of the
 | 
						|
        attribute is unloaded.
 | 
						|
 | 
						|
        .. note::
 | 
						|
 | 
						|
            The attribute history system tracks changes on a **per flush
 | 
						|
            basis**. Each time the :class:`.Session` is flushed, the history
 | 
						|
            of each attribute is reset to empty.   The :class:`.Session` by
 | 
						|
            default autoflushes each time a :class:`_query.Query` is invoked.
 | 
						|
            For
 | 
						|
            options on how to control this, see :ref:`session_flushing`.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :attr:`.AttributeState.history`
 | 
						|
 | 
						|
            :func:`.attributes.get_history` - underlying function
 | 
						|
 | 
						|
        .. versionadded:: 0.9.0
 | 
						|
 | 
						|
        """
 | 
						|
        return self.state.get_history(self.key, PASSIVE_OFF ^ INIT_OK)
 | 
						|
 | 
						|
 | 
						|
class PendingCollection(object):
 | 
						|
    """A writable placeholder for an unloaded collection.
 | 
						|
 | 
						|
    Stores items appended to and removed from a collection that has not yet
 | 
						|
    been loaded. When the collection is loaded, the changes stored in
 | 
						|
    PendingCollection are applied to it to produce the final result.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.deleted_items = util.IdentitySet()
 | 
						|
        self.added_items = util.OrderedIdentitySet()
 | 
						|
 | 
						|
    def append(self, value):
 | 
						|
        if value in self.deleted_items:
 | 
						|
            self.deleted_items.remove(value)
 | 
						|
        else:
 | 
						|
            self.added_items.add(value)
 | 
						|
 | 
						|
    def remove(self, value):
 | 
						|
        if value in self.added_items:
 | 
						|
            self.added_items.remove(value)
 | 
						|
        else:
 | 
						|
            self.deleted_items.add(value)
 |