summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py')
-rw-r--r--venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py1136
1 files changed, 0 insertions, 1136 deletions
diff --git a/venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py b/venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py
deleted file mode 100644
index 03b81f9..0000000
--- a/venv/lib/python3.11/site-packages/sqlalchemy/orm/state.py
+++ /dev/null
@@ -1,1136 +0,0 @@
-# orm/state.py
-# Copyright (C) 2005-2024 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.
-
-"""
-
-from __future__ import annotations
-
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generic
-from typing import Iterable
-from typing import Optional
-from typing import Set
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
-import weakref
-
-from . import base
-from . import exc as orm_exc
-from . import interfaces
-from ._typing import _O
-from ._typing import is_collection_impl
-from .base import ATTR_WAS_SET
-from .base import INIT_OK
-from .base import LoaderCallableStatus
-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
-from ..util.typing import Literal
-from ..util.typing import Protocol
-
-if TYPE_CHECKING:
- from ._typing import _IdentityKeyType
- from ._typing import _InstanceDict
- from ._typing import _LoaderCallable
- from .attributes import AttributeImpl
- from .attributes import History
- from .base import PassiveFlag
- from .collections import _AdaptedCollectionProtocol
- from .identity import IdentityMap
- from .instrumentation import ClassManager
- from .interfaces import ORMOption
- from .mapper import Mapper
- from .session import Session
- from ..engine import Row
- from ..ext.asyncio.session import async_session as _async_provider
- from ..ext.asyncio.session import AsyncSession
-
-if TYPE_CHECKING:
- _sessions: weakref.WeakValueDictionary[int, Session]
-else:
- # late-populated by session.py
- _sessions = None
-
-
-if not TYPE_CHECKING:
- # optionally late-provided by sqlalchemy.ext.asyncio.session
-
- _async_provider = None # noqa
-
-
-class _InstanceDictProto(Protocol):
- def __call__(self) -> Optional[IdentityMap]: ...
-
-
-class _InstallLoaderCallableProto(Protocol[_O]):
- """used at result loading time to install a _LoaderCallable callable
- upon a specific InstanceState, which will be used to populate an
- attribute when that attribute is accessed.
-
- Concrete examples are per-instance deferred column loaders and
- relationship lazy loaders.
-
- """
-
- def __call__(
- self, state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
- ) -> None: ...
-
-
-@inspection._self_inspects
-class InstanceState(interfaces.InspectionAttrInfo, Generic[_O]):
- """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)
- >>> insp.attrs.nickname.history
- History(added=['new nickname'], unchanged=(), deleted=['nickname'])
-
- .. seealso::
-
- :ref:`orm_mapper_inspection_instancestate`
-
- """
-
- __slots__ = (
- "__dict__",
- "__weakref__",
- "class_",
- "manager",
- "obj",
- "committed_state",
- "expired_attributes",
- )
-
- manager: ClassManager[_O]
- session_id: Optional[int] = None
- key: Optional[_IdentityKeyType[_O]] = None
- runid: Optional[int] = None
- load_options: Tuple[ORMOption, ...] = ()
- load_path: PathRegistry = PathRegistry.root
- insert_order: Optional[int] = None
- _strong_obj: Optional[object] = None
- obj: weakref.ref[_O]
-
- committed_state: Dict[str, Any]
-
- modified: bool = False
- expired: bool = False
- _deleted: bool = False
- _load_pending: bool = False
- _orphaned_outside_of_session: bool = False
- is_instance: bool = True
- identity_token: object = None
- _last_known_values: Optional[Dict[str, Any]] = None
-
- _instance_dict: _InstanceDictProto
- """A weak reference, or in the default case a plain callable, that
- returns a reference to the current :class:`.IdentityMap`, if any.
-
- """
- if not TYPE_CHECKING:
-
- def _instance_dict(self):
- """default 'weak reference' for _instance_dict"""
- return None
-
- expired_attributes: Set[str]
- """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."""
-
- callables: Dict[str, Callable[[InstanceState[_O], PassiveFlag], Any]]
- """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.
-
- """
-
- if not TYPE_CHECKING:
- callables = util.EMPTY_DICT
-
- def __init__(self, obj: _O, manager: ClassManager[_O]):
- self.class_ = obj.__class__
- self.manager = manager
- self.obj = weakref.ref(obj, self._cleanup)
- self.committed_state = {}
- self.expired_attributes = set()
-
- @util.memoized_property
- def attrs(self) -> util.ReadOnlyProperties[AttributeState]:
- """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.ReadOnlyProperties(
- {key: AttributeState(self, key) for key in self.manager}
- )
-
- @property
- def transient(self) -> bool:
- """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) -> bool:
- """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) -> bool:
- """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) -> bool:
- """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.
-
- .. 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) -> bool:
- """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`.
-
- .. seealso::
-
- :ref:`session_object_states`
-
- """
- return self.key is not None and self._attached and not self._deleted
-
- @property
- def detached(self) -> bool:
- """Return ``True`` if the object is :term:`detached`.
-
- .. seealso::
-
- :ref:`session_object_states`
-
- """
- return self.key is not None and not self._attached
-
- @util.non_memoized_property
- @util.preload_module("sqlalchemy.orm.session")
- def _attached(self) -> bool:
- return (
- self.session_id is not None
- and self.session_id in util.preloaded.orm_session._sessions
- )
-
- def _track_last_known_value(self, key: str) -> None:
- """Track the last known value of a particular key after expiration
- operations.
-
- .. versionadded:: 1.3
-
- """
-
- lkv = self._last_known_values
- if lkv is None:
- self._last_known_values = lkv = {}
- if key not in lkv:
- lkv[key] = NO_VALUE
-
- @property
- def session(self) -> Optional[Session]:
- """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) -> Optional[AsyncSession]:
- """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) -> Optional[_O]:
- """Return the mapped object represented by this
- :class:`.InstanceState`.
-
- Returns None if the object has been garbage collected
-
- """
- return self.obj()
-
- @property
- def identity(self) -> Optional[Tuple[Any, ...]]:
- """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) -> Optional[_IdentityKeyType[_O]]:
- """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.
-
-
- """
- return self.key
-
- @util.memoized_property
- def parents(self) -> Dict[int, Union[Literal[False], InstanceState[Any]]]:
- return {}
-
- @util.memoized_property
- def _pending_mutations(self) -> Dict[str, PendingCollection]:
- return {}
-
- @util.memoized_property
- def _empty_collections(self) -> Dict[str, _AdaptedCollectionProtocol]:
- return {}
-
- @util.memoized_property
- def mapper(self) -> Mapper[_O]:
- """Return the :class:`_orm.Mapper` used for this mapped object."""
- return self.manager.mapper
-
- @property
- def has_identity(self) -> bool:
- """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: Iterable[InstanceState[_O]],
- session: Session,
- to_transient: bool = False,
- ) -> None:
- 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: Optional[Session] = None) -> None:
- if session:
- InstanceState._detach_states([self], session)
- else:
- self.session_id = self._strong_obj = None
-
- def _dispose(self) -> None:
- # used by the test suite, apparently
- self._detach()
-
- def _cleanup(self, ref: weakref.ref[_O]) -> None:
- """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
-
- @property
- def dict(self) -> _InstanceDict:
- """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: Any, **kwargs: Any) -> None:
- self, instance, args = mixed[0], mixed[1], mixed[2:] # noqa
- manager = self.manager
-
- manager.dispatch.init(self, args, kwargs)
-
- try:
- manager.original_init(*mixed[1:], **kwargs)
- except:
- with util.safe_reraise():
- manager.dispatch.init_failure(self, args, kwargs)
-
- def get_history(self, key: str, passive: PassiveFlag) -> History:
- return self.manager[key].impl.get_history(self, self.dict, passive)
-
- def get_impl(self, key: str) -> AttributeImpl:
- return self.manager[key].impl
-
- def _get_pending_mutation(self, key: str) -> PendingCollection:
- if key not in self._pending_mutations:
- self._pending_mutations[key] = PendingCollection()
- return self._pending_mutations[key]
-
- def __getstate__(self) -> Dict[str, Any]:
- state_dict: Dict[str, Any] = {
- "instance": self.obj(),
- "class_": self.class_,
- "committed_state": self.committed_state,
- "expired_attributes": self.expired_attributes,
- }
- state_dict.update(
- (k, self.__dict__[k])
- for k in (
- "_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: Dict[str, Any]) -> None:
- inst = state_dict["instance"]
- if inst is not None:
- self.obj = weakref.ref(inst, self._cleanup)
- self.class_ = inst.__class__
- else:
- self.obj = lambda: None # type: ignore
- 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"]
-
- self.expired_attributes = state_dict["expired_attributes"]
- 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:
- self.identity_token = self.key[2]
-
- 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_: _InstanceDict, key: str) -> None:
- """Remove the given attribute and any
- callables associated with it."""
-
- old = dict_.pop(key, None)
- manager_impl = self.manager[key].impl
- if old is not None and is_collection_impl(manager_impl):
- manager_impl._invalidate_collection(old)
- self.expired_attributes.discard(key)
- if self.callables:
- self.callables.pop(key, None)
-
- def _copy_callables(self, from_: InstanceState[Any]) -> None:
- if "callables" in from_.__dict__:
- self.callables = dict(from_.callables)
-
- @classmethod
- def _instance_level_callable_processor(
- cls, manager: ClassManager[_O], fn: _LoaderCallable, key: Any
- ) -> _InstallLoaderCallableProto[_O]:
- impl = manager[key].impl
- if is_collection_impl(impl):
- fixed_impl = impl
-
- def _set_callable(
- state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
- ) -> None:
- if "callables" not in state.__dict__:
- state.callables = {}
- old = dict_.pop(key, None)
- if old is not None:
- fixed_impl._invalidate_collection(old)
- state.callables[key] = fn
-
- else:
-
- def _set_callable(
- state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
- ) -> None:
- if "callables" not in state.__dict__:
- state.callables = {}
- state.callables[key] = fn
-
- return _set_callable
-
- def _expire(
- self, dict_: _InstanceDict, modified_set: Set[InstanceState[Any]]
- ) -> None:
- 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_: _InstanceDict,
- attribute_names: Iterable[str],
- no_loader: bool = False,
- ) -> None:
- 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 is_collection_impl(impl) and old is not NO_VALUE:
- impl._invalidate_collection(old)
-
- lkv = self._last_known_values
- if lkv is not None and key in lkv and old is not NO_VALUE:
- lkv[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: InstanceState[_O], passive: PassiveFlag
- ) -> LoaderCallableStatus:
- """__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) -> Set[str]:
- """Return the set of keys which have no uncommitted changes"""
-
- return set(self.manager).difference(self.committed_state)
-
- def unmodified_intersection(self, keys: Iterable[str]) -> Set[str]:
- """Return self.unmodified.intersection(keys)."""
-
- return (
- set(keys)
- .intersection(self.manager)
- .difference(self.committed_state)
- )
-
- @property
- def unloaded(self) -> Set[str]:
- """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
- @util.deprecated(
- "2.0",
- "The :attr:`.InstanceState.unloaded_expirable` attribute is "
- "deprecated. Please use :attr:`.InstanceState.unloaded`.",
- )
- def unloaded_expirable(self) -> Set[str]:
- """Synonymous with :attr:`.InstanceState.unloaded`.
-
- This attribute was added as an implementation-specific detail at some
- point and should be considered to be private.
-
- """
- return self.unloaded
-
- @property
- def _unloaded_non_object(self) -> Set[str]:
- return self.unloaded.intersection(
- attr
- for attr in self.manager
- if self.manager[attr].impl.accepts_scalar_loader
- )
-
- def _modified_event(
- self,
- dict_: _InstanceDict,
- attr: Optional[AttributeImpl],
- previous: Any,
- collection: bool = False,
- is_userland: bool = False,
- ) -> None:
- 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 TYPE_CHECKING:
- assert is_collection_impl(attr)
- 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
-
- lkv = self._last_known_values
- if lkv is not None and attr.key in lkv:
- lkv[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_t()
-
- 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_: _InstanceDict, keys: Iterable[str]) -> None:
- """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_: _InstanceDict, instance_dict: Optional[IdentityMap] = None
- ) -> 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_: Iterable[Tuple[InstanceState[Any], _InstanceDict]],
- instance_dict: Optional[IdentityMap] = None,
- ) -> 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:
- """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
-
- """
-
- __slots__ = ("state", "key")
-
- state: InstanceState[Any]
- key: str
-
- def __init__(self, state: InstanceState[Any], key: str):
- self.state = state
- self.key = key
-
- @property
- def loaded_value(self) -> Any:
- """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) -> Any:
- """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) -> History:
- """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) -> History:
- """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
-
- """
- return self.state.get_history(self.key, PASSIVE_OFF ^ INIT_OK)
-
-
-class PendingCollection:
- """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.
-
- """
-
- __slots__ = ("deleted_items", "added_items")
-
- deleted_items: util.IdentitySet
- added_items: util.OrderedIdentitySet
-
- def __init__(self) -> None:
- self.deleted_items = util.IdentitySet()
- self.added_items = util.OrderedIdentitySet()
-
- def merge_with_history(self, history: History) -> History:
- return history._merge(self.added_items, self.deleted_items)
-
- def append(self, value: Any) -> None:
- if value in self.deleted_items:
- self.deleted_items.remove(value)
- else:
- self.added_items.add(value)
-
- def remove(self, value: Any) -> None:
- if value in self.added_items:
- self.added_items.remove(value)
- else:
- self.deleted_items.add(value)